46

Today I've just seen this article which described the relevance of SOLID principle in F# development-

F# and Design principles – SOLID

And while addressing the last one - "Dependency inversion principle", the author said:

From a functional point of view, these containers and injection concepts can be solved with a simple higher order function, or hole-in-the-middle type pattern which are built right into the language.

But he didn't explain it further. So, my question is, how is the dependency inversion related to higher order functions?

5 Answers 5

45

Dependency Inversion in OOP means that you code against an interface which is then provided by an implementation in an object.

Languages that support higher language functions can often solve simple dependency inversion problems by passing behaviour as a function instead of an object which implements an interface in the OO-sense.

In such languages, the function's signature can become the interface and a function is passed in instead of a traditional object to provide the desired behaviour. The hole in the middle pattern is a good example for this.

It let's you achieve the same result with less code and more expressiveness, as you don't need to implement a whole class that conforms to an (OOP) interface to provide the desired behaviour for the caller. Instead, you can just pass a simple function definition. In short: Code is often easier to maintain, more expressive and more flexible when one uses higher order functions.

An example in C#

Traditional approach:

public IEnumerable<Customer> FilterCustomers(IFilter<Customer> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter.Matches(customer))
        {
            yield return customer;
        }
    }
}

//now you've got to implement all these filters
class CustomerNameFilter : IFilter<Customer> /*...*/
class CustomerBirthdayFilter : IFilter<Customer> /*...*/

//the invocation looks like this
var filteredDataByName = FilterCustomers(new CustomerNameFilter("SomeName"), customers);
var filteredDataBybirthDay = FilterCustomers(new CustomerBirthdayFilter(SomeDate), customers);

With higher order functions:

public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter(customer))
        {
            yield return customer;
        }
    }
}

Now the implementation and invocation become less cumbersome. We don't need to supply an IFilter implementation anymore. We don't need to implement classes for the filters anymore.

var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);

Of course, this can already be done by LinQ in C#. I just used this example to illustrate that it's easier and more flexible to use higher order functions instead of objects which implement an interface.

14
  • 3
    Nice example. However, like Gulshan I'm trying to find out more about functional programming and I was wondering if this kind of "functional DI" doesn't sacrifice some rigor and significance compared to "object oriented DI". The higher order signature only states that the function passed must take a Customer as a parameter and return a bool whereas the OO version enforces the fact that the object passed is a filter (implements IFilter<Customer>). It also makes the notion of filter explicit, which could be a good thing if it is a core concept of the Domain (see DDD). What do you think ? Aug 25, 2011 at 8:26
  • 2
    @ian31: This is an interesting topic indeed! Anything that is passed to FilterCustomer will behave as some sort of filter implicitly. When the filter concept is an essential part of the domain and you need complex filter rules that are used multiple times accross the system, it's sure better to encapsulate them. If not or only to a very low degree, then I'd aim for technical simplicity and pragmatism.
    – Falcon
    Aug 25, 2011 at 8:37
  • 6
    @ian31: I completely disagree. Implementing IFilter<Customer> is no enforcement at all. The higher-order function is vastly more flexible, which is a big benefit, and being able to write them inline is another huge benefit. Lambdas are also much easier able to capture local variables.
    – DeadMG
    Aug 25, 2011 at 11:53
  • 3
    @ian31: The function can also be verified at compile-time. You could also write a function, name it, and then pass it as an argument as long as it fullfills the obvious contract (takes customer, returns bool). You needn't necessarily pass a lambda expression. So you can cover that lack of expressiveness to a certain degree. However, the contract and its intent is not as clearly expressed. That's a major disadvantage sometimes. All in all it's a matter of expressiveness, language and encapsulation. I think you have to judge each case by itself.
    – Falcon
    Aug 25, 2011 at 12:20
  • 2
    if you feel strongly about clarifying the semantic meaning of an injected function, you can in C# name function signatures using delegates: public delegate bool CustomerFilter(Customer customer). in pure functional languages like haskell, aliasing types is trivial: type customerFilter = Customer -> Bool
    – sara
    May 12, 2016 at 14:39
10

If you want to change the behaviour of a function

doThis(Foo)

you could pass another function

doThisWith(Foo, anotherFunction)

which implements the behaviour you want to be different.

"doThisWith" is a higher-order function because it takes another function as an argument.

For example you could have

storeValues(Foo, writeToDatabase)
storeValues(Foo, imitateDatabase)
6

Short Answer:

Classical Dependency Injection/Inversion of Control uses a class interfaces as placeholder for dependant functionality. This interface is implemented by a class.

Instead of Interface/ClassImplementation many dependencies can be easier implemented with a delegate function.

You find an example for both in c# at ioc-factory-pros-and-contras-for-interface-versus-delegates.

2

Piggy-backing off of LennyProgrammers example...

One of the things that the other examples missed is that you can use higher order functions together with partial function application (PFA) to bind (or "inject") dependencies into a function (via its argument list) to create a new function.

If instead of:

doThisWith(Foo, anotherFunction)

we (to be conventional in how P.F.A. usually is done) have the low level worker function as (swapping arg order):

doThisWith( anotherFunction, Foo )

We can then partially apply doThisWith like so:

doThis = doThisWith( anotherFunction )  // note that "Foo" is still missing, argument list is partial

Which allows us later to use the new function like so:

doThis(Foo)

Or even:

doThat = doThisWith( yetAnotherDependencyFunction )
...
doThat( Bar )

See also: https://ramdajs.com/docs/#partial

... and, yeah, adders/multipliers are unimaginative examples. A better example would be a function that takes messages, and logs them or emails them depending on what the "consumer" function passed in as a dependency was.

Extending this idea, still longer argument lists can be progressively narrowed down to increasingly specialized functions with shorter and shorter argument lists, and of course any of these such functions can be passed to other functions as dependencies to be partially applied.

OOP is nice if you need a bundle of things with multiple closely related operations, but it turns into make-work to make a bunch of classes each with a single public "do it" method, a la "The Kingdom of Nouns".

1
  • Your explanation made me understand the naming “dependency injection” better, when you talk about consumer Sep 4, 2021 at 8:58
1

Compare this:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = new LinkedList<String>();
for (String name : names) {
    if (name.startsWith("S")) {
        namesBeginningWithS.add(name);
    }
}

with:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = names.stream().filter(n <- n.startsWith("S")).collect();

The second version is Java 8's way of reducing boilerplate code (looping etc) by providing higher-order functions like filter which allows you to pass the bare minimum (i.e. the dependency to be injected - the lambda expression).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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