
Functional approaches to dependency injection (2016) - amzans
https://fsharpforfunandprofit.com/posts/dependency-injection-1/
======
RobertRoberts
Can someone clarify if this author's methods are actually inline with the core
principles of functional programming?

I've been doing OOPs from very early in my career (I run into FP from time to
time but never as a core of my own work), and much of the example he's
implementing here seems like make-work simply to enforce a specific paradigm.
Am I missing something?

For example, at first his concepts seem brilliant, until you get to the
refactoring steps, which feel painful.

Is this the end result of high-level extreme veteran coders use of FP? With
OOPs, simple inheritance & factories seems like it would solve all the
dependency issues he's addressing.

~~~
bunderbunder
I've always found ScottW to be pretty pragmatic. If Martin Odersky favors
multiparadigm programming that nonetheless leans toward object-oriented
methods, Scott Wlaschin favors leaning toward functional methods as a default.

As far as whether he's in line with "core principles of functional
programming", that's maybe a phrase that's well-designed to start a fight.
Even more so than in object-oriented languages, different ways of doing things
tend to cluster around different functional languages. In broad strokes, what
he's doing seems fine coming from a F# developer. I think it would translate
well enough to Haskell, though I'm nowhere near as experienced in that
language. In Clojure, on the other hand, I'm guessing you'd want to do
something completely different.

------
default-kramer
> The downside of course, is that there are now five extra parameters for the
> function, which looks painful. (Of course, the equivalent method in the OO
> version also had these five dependencies, but they were implicit). In my
> opinion though, this pain is actually helpful!

I'm definitely in favor of making dependencies explicit, but I wish there was
a typed language that allowed me to avoid mentioning "less important"
dependencies when I want to. Logging is a perfect example:

    
    
        function log(msg: string) =
            ImplicitDependencies<ILogInfo>().Write(msg)
    

Let's imagine that "ImplicitDependencies" is some special keyword. The
compiler would give the "log" function a type of (string -> (implicit
ILogInfo) -> unit). But my code is allowed to say that its type is (string ->
unit) if it wants. Now if I use this "log" function in UpdateCustomerProfile,
then UpdateCustomerProfile gets an (implicit ILogInfo) parameter too.

The compiler would also give me some way to designate a function as "root
level" meaning that all implicit dependencies must be resolved. I'd imagine
something like Racket's parameterize for this. ([https://docs.racket-
lang.org/guide/parameterize.html](https://docs.racket-
lang.org/guide/parameterize.html))

The idea is, most of the time, I don't want to keep mentioning logging in my
type signatures. I just want to silently kick it up the call stack.

~~~
bunderbunder
This is where I start to like Scala's traits. Being able to declare a class
"with logging" and automatically get a pre-configured loger handle with no
extra ceremony needed, is really convenient.

It's also unprincipled as heck, from both an object-oriented and a functional
perspective. I think my alignment in the logging department might be chaotic
good.

~~~
default-kramer
Nice, I don't do much Scala so I never thought about using traits for that! Is
that a common pattern?

It seems similar to traditional OO dependency injection, except without all
the ceremony of adding a constructor parameter and corresponding class member
to hold it.

~~~
bunderbunder
I'm not entirely sure how common it is.

It's a case of multiple implementation inheritance, so I could see a lot of
people balking at it.

------
kyleperik
For languages that support monkey patching you don't dependency injection.

A lot of the design patterns I used to adore and look at as if they were rare
gems in a sea of bad code. Over time my opinions have changed. Many patterns
are good, but I don't really like to think of those as patterns.

A pattern implies repetition. Something that you can used each time you have a
situation that warrents it. In my opinion, this openly contradicts "Don't
repeat yourself".

So I think we should start trading design patterns for simple and well
designed languages. No code always beats good code.

~~~
bunderbunder
> For languages that support monkey patching you don't dependency injection.

What am I missing here? We've _got_ to be working with different definitions
of the term "dependency injection" or something.

Using monkey patching as a complete replacement for dependency injection seems
absolutely bonkers to me. The codebase I'm imagining when I here that is one
where no module's run-time behavior can be understood by simply reading its
source code, because every single one of its run-time dependencies is being
monkey patched in from some where else entirely. It's also one where, to take
the same class and instantiate two different copies with two different sets of
run-time dependencies, I'd have to do something wacky with the monkey patching
instead of just creating them with different constructor arguments.

~~~
kyleperik
I know what you're talking about, I realize that's how most people think of as
monkey patching. That's what I thought at first. But when I tried structuring
a python project with Dependency Injection it seemed overly complex.

Instead of just making modules I had to put everything in a class. I had to
write code to decide what things are being injected where. It didn't look like
python anymore, so I stopped doing it, and I've never had problems, and I've
written much less code.

I only ever use monkey patching for testing. People talk about how it makes it
easier to switch out other implementations, but in reality it's easier. You
just import a different module.

------
smarrenbartlet
Not once have I ever seen a codebase where OO 'design patterns' have led to
ease of development or modularity, every time they have made development
worse. These principles should be kept as far away from functional programming
as possible.

------
wtetzner
If F# had taken OCaml's module system, functors would be the obvious way to do
dependency injection, and would have been supported out-of-the-box.

I realize there were difficulties in integrating with the rest of the .NET
platform/ecosystem, but it's really too bad that the module system was left
out, as it's one of the best parts about OCaml.

~~~
Nelkins
What can you achieve using OCaml modules that you can't do using objects with
a constructor in F#?

~~~
wtetzner
There are a few advantages of modules. One is that they can encapsulate types
along-side functions/values. Also, it can be advantageous that functors are
applied at compile time.

The really cool one though is abstract types. You can distinguish types purely
in the type system, with no change to run-time representation, using module
signatures that don't specify the concrete type of a type entry.

------
oweiler
Sadly there has never been Part 2.

~~~
megaman22
It does seem like most of his efforts were redirected into his book lately,
which is understandable. I picked it up a while back and have to say I'd
recommend it.

