Hacker News .hnnew | past | comments | ask | show | jobs | submitlogin

The three DI patterns I've used in Haskell are:

1) Record of functions. Probably the most common, and pretty analogous to calling a constructor in OO. If you've heard "objects are a poor man's closures, and vice-versa", that's what this is referring to. You build a closure with as many dependencies as you can pass in at startup time (fields in OO), then later you can just call one of the functions inside it, with the remaining parameters (method call in OO).

2) final tagless. After you're comfortable with an Optional<Value> that might return a Value, and Future<Value> that will return a Value later, you can venture further down that path and work with a Writer<Value> which will return a value and do some logging, or a Reader<Env, Value> which will return a value while having read-access to some global config. But what if you want many of these at once? You end up with a ReaderT Env (Writer Value) or some nonsense. So instead you can write your code in terms of m<Value>, and then constrain m appropriately. A function which can access Git, do logging, and run commands on the CLI might have type:

  (Git m, Logging m, Cli m) => m Value.
But that function does not know what m is (so therefore does not depend on it) You might like to have different m's for unit tests, integration tests, a live test environment and a live prod environment, for instance.

3) Free Monad & interpreter pattern. A nifty way to build a DSL describing the computations you'd like to do, and then you can write different interpreters to execute those computations in different ways. For instance, you could write a network/consensus algorithm, and have one interpreter execute the whole thing in memory with pure functions, for rapid development, debugging and iterating, and another interpreter be the real networked code. It's fun, but someone wrote an article about how Free Monads are just a worse version of final tagless, so I haven't used this in a while.



Yes, and for interested parties it's probably worth elaborating that Git might be something like

    class Git m where
        clone :: Url -> m GitRepo
        currentHead :: GitRepo -> m CommitHash
and Logging might be something like

    class Logging m where
        log :: String -> m ()
which is very similar to defining

    data GitDict m = MkGitDict {
        clone :: Url -> m GitRepo,
        currentHead :: GitRepo -> m CommitHash
    }

    data LoggingDict m = MkLoggingDict {
        log :: String -> m ()
    }

    class Git m where gitDict :: GitDict m
    class Logging m where loggingDict :: LoggingDict m
So the "record of functions" style and the "final tagless" style are equivalent, except that the former passes operations manually and the latter passes operations implicitly. The former can be considered more cumbersome, but is more flexible.

If you're interested in how my effect system Bluefin does this, you can have a look at https://hackage.haskell.org/package/bluefin-0.0.4.2/docs/Blu...


With (3) it’s hard to have different instruction sets for different parts of your program. Ideally, a function that logs only uses the log instruction set and this is reflected in the type. Once you start down this road, you end up at final tagless anyway.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: