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

Dependency inversion is a OOP pattern?


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.


Yes, the dependency inversion principle is not a commonly held principle in FP or imperative paradigms.


Wow when I read this comment I did a double take and had to go to Wikipedia… then I realized dependency inversion is not the same thing as inversion of control and things made much more sense.

I guess part of the confusion came from how dependency injection is a form of inversion of control… the words are all very similar to dependency inversion.


I think your identification of that distinction is entirely too generous. Typically the derision of dependency inversion extends to inversion of control since they are cut from the same cloth. One just focuses on what is being inverted and the other the process of inversion.


I haven't internalised what inversion of control means, but I'm very strong on the distinction between dependency inversion and dependency injection frameworks.

With DI, you stop your business logic from knowing about your Postgres database.

With DInjF, not only does business logic still know about Postres, but now it knows about Spring too! (The upside is that it takes fewer lines of code to get from 0 to spaghetti)


So passing functions to functions instead of explicitly calling them is what exactly then?


Higher order functions can be used for dependency injection.

Dependency injection and the Dependency inversion principle are not one and the same.

The principle makes a claim, that inversion is a good onto itself.

Injection is a tool not a claim.


The principle is not making any claim. It is simply a method to achieve something.


Dependency injection


Dependency Inversion is a recipe for how to invert the dependency between two components (by introducing a third component on which both depend and which can be grouped with either side, hence allowing to invert the dependency at will). It’s not inherently tied to OOP.

Incidentally, one thing it glosses over is the creation of the components, which may prevent completely inverting the dependency.




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

Search: