Question

I've written my first moderately large project in functional style (in F#) and can see the advantages. The main challenge was to achieve the "Onion" architecture i.e. large and "smart" pure core / thin and "dumb" impure shell. It took most of the time but also yielded largest benefits (better testability, correctness, code reusability etc.) and I want to know more.

I began to search for common approach to segregate IO/effects from pure logic and found a lot of material about Free Monads, mostly in Haskell (I know that F# doesn't have HKT's nor native free monads but anyway).

From what I've read, do I understand it correctly that:

  • Free monad is not the recipe to achieve Onion architecture, but instead Onion architecture is a prerequisite to take the most of Free monad
  • Free monad on top of messy undisciplined code has little if any advantage over plain Dependency Injection

For example if results of IO operations (abstracted by Free monad or not) used for branching/decision making interleaved with other logic without any discipline - in short, if function doesn't reduce to a flat list or a very simple tree of instructions that can be further simplified by the interpreter of free monad.

Am I missing something fundamental here?

Here's one of the articles I can refer to: http://degoes.net/articles/modern-fp
In "Enjoying the Onion" paragraph author claims that "We are further able to completely untangle different aspects of our program ... finally, we are able to consolidate knowledge that would otherwise be distributed throughout the program ..." - allegedly thanks to Free?

But from what I can see from the article he first carefully designed the onion and the concise File algebra and only then got the aforementioned benefits (different aspects untangled/knowledge consolidated/opportunity to optimize some compound operations like Rename in interpreter etc.). When this already achieved I still struggle to see how Free is "far better solution" (a citation from "Summary") than say a DI container.

Was it helpful?

Solution

Mostly what free monads are in relation to an onion architecture are a way to easily swap the outer layers of the onion. In practice, usually you swap in a pure interpreter implementation for unit testing, and an impure implementation for normal runtime. However, it is also sometimes used for swapping out things like different database or logging methods.

The main advantage over OOP-style dependency injection is that with free monads, you inject your dependencies only once at the edge of the world at interpretation time. With OOP-style, you have to pass that dependency everywhere throughout the program specification part of the code. With free monads, your code reads more concisely, because you don't have extra function arguments that do nothing but pass dependencies to lower layers to enable unit testing.

That advantage doesn't go away even if your code is "undisciplined," although discipline obviously benefits any code in any paradigm.

Licensed under: CC-BY-SA with attribution
scroll top