suggestions that one might need to understand category theory and similar such nonsense
It’s definitely unnecessary to understand category theory to work with monads. What is helpful, however, is having some comfort with math.
What does that mean exactly? It’s a level of comfort in working with definitions, properties, operations, special elements, proofs. People can get so frustrated because they think they don’t understand what a monad is. Like they want to hold it in their hand the way they would an apple or a tennis ball.
When you’re comfortable with math you kind of lose that need to think about an object concretely. You start to only care about the definitions, properties, axioms, laws, theorems, etc that concern a particular object. Then you just play around with a few examples and see the implications of these things. That’s all there is to it. The power comes from the abstraction. It can take time to become comfortable with abstract concepts though.
Another thing that is often missed is that we think we “understand” something when in fact we just got used to it. Even such simple concept as “number” would probably be very difficult to explain to someone (who either doesn’t know what a number is or wishes to “really understand” it).
Oh yeah. The history of numbers is long and complicated. I think today we take for granted the idea that numbers are objects (in some abstract sense). In the past, there was simply no concept of number as a thing. Numbers were used for counting or measuring, so they existed only as adjectives attached to their objects of counting/measuring, not nouns in their own rite.
Well the natural numbers are the decatigorification [1] of the category of finite sets.
I won't speak to other kinds of numbers but it's funny how category theory helps answer that question as well.
Which, in FP, is all but invisible: the arguments to a function literally are its dependencies.
I think OOP has more primitive concepts (and more mutation) than FP, so dependency injection in OOP also includes object construction and often mocking effectful operations. That's why it gets its own name in OOP, while being more of an ambient idea in FP.
I don't think I agree with that. In scheme you can write (display "hello world") inside a function and this is directed to some globally configured port. If either the port or the display function was passed as a parameter to the function, then it would be dependency injection.
On its face, you're right. You can formalize this approach using dynamically-scoped variables, which is a step on the road toward coeffect systems in typed functional languages. But in a coeffect system, the type of `display` would explicitly call out that its environment must provide the necessary dynamic variables, which brings us back to dependency injection. `display`'s dependencies would be injected via dynamic scope, which you can override in the caller by defining a dynamic binding.
As an alternative, I would suggest that `display` is special, and that instead of thinking of passing an extra parameter to `display`, that the module that calls it should instead have `display` itself injected.
Yes that is actually what I meant, I guess I was not being clear. The function calling "display" could have "display" passed as an argument rather than calling it as a globally defined function.
Relying on dynamic variables would probably not be considered dependency injection. A major purpose of DI is that dependencies should be declared in the signature, making it explicit which dependencies a unction or object depends on.
> A major purpose of DI is that dependencies should be declared in the signature
Yes, and a coeffect system would cause dependencies on dynamic variables to be declared statically, even if they're provided by "the environment" at runtime.
Tomas Petricek's PhD project page is a good introduction to coeffect systems, and it illustrates dynamic variables as an example. http://tomasp.net/coeffects/
I used to restrict DI as dynamic binding but someone told me that for business, having a special category of arguments to be tweaked as see fit is useful. It's variable at the system level maybe ?
Isn't that Inversion Of Control, not Dependency Injection? I've always thought of Dependency Injection as about "declaring a class in terms of its dependencies' interfaces, but allowing a framework to take responsibility for instantiating the actual dependency objects" - whereas "a function that accepts a function" is IoC (e.g. https://kentcdodds.com/blog/inversion-of-control/)
They're very closely related concepts - both abstractly saying "let me define unit of logic in terms of how it composes passed-in units of logic", but the distinction between "objects that need to be instantiated and injected at construction time" and "functions that are passed in at runtime" is pretty large.
I think the distinction may blurry a bit, if you consider classes as a fancy way of writing higher order functions.
i.e:
You can think of a class as a higher order function, that takes in a list of arguments (constructor), and returns a list of functions, that are defined within the closure of those arguments
--
Hence, to me, the essence of these things are the same -- thought you are right that when people talk about dependency injection, they are also implying a specific way that these dependencies are provided (by the framework, usually magically)
> You can think of a class as a higher order function, that takes in a list of arguments (constructor), and returns a list of functions, that are defined within the closure of those arguments
That's a really great definition of a class (when used as a "service" class, rather than a data class or an enum class, for example). And to me, shows that FP and OOP are really two sides of the same coin. Limit OOP to immutable data structures, and you get something extremely close to FP.
> Isn't that Inversion Of Control, not Dependency Injection?
Dependency injection is one of the techniques used to implement inversion of control.
> I've always thought of Dependency Injection as about "declaring a class in terms of its dependencies' interfaces, but allowing a framework to take responsibility for instantiating the actual dependency objects" - whereas "a function that accepts a function" is IoC (e.g. https://kentcdodds.com/blog/inversion-of-control/)
Nope, dependency injection is about passing dependencies to the objects that depends on them (i.e., inject the dependency), instead of letting the object itself instantiate them directly or actively request access to them.
You may or may not depend on a framework to manage dependencies (service locator) and pass them to instances (service injector) but those are just helper components that assist with the whole dependency injection workflow. The key point is that dependencies are passed to the objects that depends on them.
I feel it is the same with monads including all the false suggestions that one might need to understand category theory and similar such nonsense.