19

I have recently been reading a book entitled Functional Programming in C# and it occurs to me that the immutable and stateless nature of functional programming accomplishes similar outcomes to dependency injection patterns and is possibly even a better approach, especially in regards to unit testing.

I would be appreciative if anyone who has experience with both approaches could share their thoughts and experiences in order to answer the primary question: is Functional Programming a viable alternative to dependency injection patterns?

23

Dependency management is a big problem in OOP for the following two reasons:

  • The tight coupling of data and code.
  • Ubiquitous use of side effects.

Most OO programmers consider the tight coupling of data and code to be wholly beneficial, but it comes with a cost. Managing the flow of data through the layers is an unavoidable part of programming in any paradigm. Coupling your data and code adds the additional problem that if you want to use a function at a certain point, you have to find a way get its object to that point.

Use of side effects creates similar difficulties. If you use a side effect for some functionality, but want to be able to swap out its implementation, you pretty much have no other choice but to inject that dependency.

Consider as an example a spammer program that scrapes web pages for email addresses then emails them. If you have a DI mindset, right now you're thinking of the services you will encapsulate behind interfaces, and which services will get injected where. I'll leave that design as an exercise for the reader. If you have an FP mindset, right now you're thinking of the inputs and outputs for the lowest layer of functions, like:

  • Input a web page address, output the text of that page.
  • Input a page's text, output a list of links from that page.
  • Input a page's text, output a list of email addresses on that page.
  • Input a list of email addresses, output a list of email addresses with duplicates removed.
  • Input an email address, output a spam email for that address.
  • Input a spam email, output the SMTP commands to send that email.

When you think in terms of inputs and outputs, there are no function dependencies, only data dependencies. That's what makes them so easy to unit test. Your next layer up arranges for the output of one function to be fed into the input of the next, and can easily swap out the various implementations as needed.

In a very real sense, functional programming naturally prods you to always invert your function dependencies, and therefore you usually don't have to take any special measures to do so after the fact. When you do, tools like higher-order functions, closures, and partial application make it easier to accomplish with less boilerplate.

Note that it's not dependencies themselves that are problematic. It's dependencies that point the wrong way. The next layer up may have a function like:

processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses

It's perfectly okay for this layer to have dependencies hard-coded like this, because its sole purpose is to glue the lower-layer functions together. Swapping an implementation is as simple as creating a different composition:

processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses

This easy recomposition is made possible by a lack of side effects. The lower-layer functions are completely independent of each other. The next layer up may choose which processText is actually used based on some user config:

actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText

Again, not an issue because all the dependencies point one way. We don't need to invert some dependencies in order to get them all pointing the same way, because pure functions already forced us to do so.

Note that you could make this a lot more coupled by passing config down through to the lowest layer instead of checking it at the top. FP doesn't prevent you from doing this, but it does tend to make it a lot more annoying if you try.

  • 3
    "Use of side effects creates similar difficulties. If you use a side effect for some functionality, but want to be able to swap out its implementation, you pretty much have no other choice but to inject that dependency." I don't think side effects have anything to do with this. If you want to swap implementations in Haskell, you still have to do dependency injection. Desugar the type classes and you're passing around an interface as the first argument to every function. – Doval Mar 11 '15 at 14:19
  • 2
    The crux of the matter is that almost every language forces you to hard-code references to other code modules, so the only way to swap implementations is to use dynamic dispatch everywhere, and then you're stuck resolving your dependencies at run time. A module system would let you express the dependency graph at type-checking time. – Doval Mar 11 '15 at 14:24
  • @Doval--Thanks for your interesting and thought-provoking comments. I may have misunderstood you, but am I correct in inferring from your comments that if I were to use a functional style of programming over a DI style (in the traditional C# sense), then I would avoid possible debugging frustrations associated with run-time resolution of dependencies? – Matt Cashatt Mar 11 '15 at 15:15
  • @MatthewPatrickCashatt It's not a matter of style or paradigm, but of language features. If the language doesn't support modules as first-class things, you're going to have to do some form dynamic dispatch and dependency injection to swap implementations, because there's no way to express the dependencies statically. To put it a bit differently, if your C# program uses strings, it has a hard-coded dependency on System.String. A module system would let you replace System.String with a variable so that the choice of string implementation isn't hard-coded, but still resolved at compile time. – Doval Mar 11 '15 at 15:42
8

is Functional Programming a viable alternative to dependency injection patterns?

This strikes me as an odd question. Functional Programming approaches are largely tangential to dependency injection.

Sure, having immutable state can push you to not "cheat" by having side effects or using the class state as an implicit contract between functions. It makes passing of data more explicit, which I suppose is the most basic form of dependency injection. And the functional programming concept of passing functions around makes that a lot easier.

But it doesn't remove dependencies. Your operations still need all of the data/operations they needed when your state was mutable. And you still need to get those dependencies there somehow. So I wouldn't say that functional programming approaches replace DI at all, so are no sort of alternative.

If anything, they've just shown you how bad OO code can create implicit dependencies than programmers rarely think about.

  • Thanks again for contributing to the conversation, Telastyn. As you have pointed out, my question isn't very well constructed (my words), but thanks to the feedback here I am starting to understand a bit better what it is that is sparking in my brain about all of this: We all agree (I think) that unit testing can be a nightmare with out DI. Unfortunately, use of DI, especially with IoC containers can create a new form of debugging nightmare thanks to the fact that it resolves dependencies at runtime. Similar to DI, FP makes unit testing easier, but without the runtime dependency issues. – Matt Cashatt Mar 11 '15 at 15:23
  • (continued from above). . .This is my present understanding anyway. Please let me know if I am missing the mark. I don't mind admitting that I am a mere mortal among giants here! – Matt Cashatt Mar 11 '15 at 15:25
  • @MatthewPatrickCashatt - DI doesn't necessarily imply runtime dependency issues, which as you note, are horrible. – Telastyn Mar 11 '15 at 15:27
5

The quick answer to your question is: No.

But as others have asserted, the question marries two, somewhat unrelated concepts.

Let's do this step by step.

DI results in non-functional style

In the core of function programming are pure functions - functions that map input to output, so you always get the same output for a given input.

DI typically means your unit is no longer pure since the output may vary depending of the injection. For instance, in the following function:

const bookSeats = ( seatCount, getBookedSeatCount ) => { ... }

getBookedSeatCount (a function) may vary yielding different results for the same given input. This makes bookSeats impure as well.

There are exceptions for this - you may inject one of two sort algorithms that implement the same input-output mapping, albeit using different algorithms. But these are exceptions.

A system cannot be pure

The fact that a system cannot be pure is equally ignored as it is asserted in functional programming sources.

A system must have side effects with the obvious examples being:

  • UI
  • Database
  • API (in client-server architecture)

So part of your system must involve side-effects and that part may well also involve imperative style, or OO style.

The shell-core paradigm

Borrowing the terms from Gary Bernhardt's superb talk on boundaries, a good system (or module) architecture will include these two layers:

  • Core
    • Pure functions
    • Branching
    • No dependencies
  • Shell
    • Impure (side effects)
    • No branching
    • Dependencies
    • May be imperative, involve OO style, etc.

The key takeaway is to 'split' the system into it's pure part (the core) and the impure part (the shell).

Although offering a slightly flawed solution (and conclusion), this Mark Seemann's article proposes the very same concept. The Haskell implementation is particularly insightful as it shows it can all be done using FP.

DI and FP

Employing DI is perfectly reasonable even if the bulk of your application is pure. The key is to confine the DI within the impure shell.

An example will be API stubs - you want the real API in production, but use stubs in testing. Adhering to the shell-core model will help a great deal here.

Conclusion

So FP and DI are not exactly alternatives. You are likely to have both in your system, and the advice is to ensure separation between the pure and impure part of the system, where FP and DI reside respectively.

  • When you refer to the shell-core paradigm, how would one achieve no branching in the shell? I can think of many examples where an application would need to do one impure thing or another based on a value. Is this no-branching rule applicable in languages like Java? – jrahhali Feb 5 at 14:12
  • @jrahhali Please see Gary Bernhardt's Talk for details (linked in the answer). – Izhaki Feb 5 at 14:42
1

From the OOP point of view functions can be considered to be single-method interfaces.

Interface is a stronger contract than a function.

If you are using a functional approach and do a lot of DI then in comparison to using an OOP approach you will get more candidates for each dependency.

void DoStuff(Func<DateTime> getDateTime) {}; //Anything that satisfies the signature can be injected.

vs

void DoStuff(IDateTimeProvider dateTimeProvider) {}; //Only types implementing the interface can be injected.
  • 3
    Any class can be wrapped to implement the interface so the "stronger contract" isn't much stronger. More importantly giving each function a different type makes it borderline impossible to do function composition. – Doval Mar 11 '15 at 17:48
  • Functional programming doesn't mean "Programming with higher order functions", it refers to a far broader concept, higher order functions are just one technique useful in the paradigm. – Jimmy Hoffa Mar 11 '15 at 19:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.