
Modern Functional Programming: The Onion Architecture - Roxxik
http://degoes.net/articles/modern-fp-part-2
======
slashdotdash
Gary Bernhardt describes a similar architecture using a "Functional Core,
Imperative Shell" in his Boundaries talk[1].

"Purely functional code makes some things easier to understand: because values
don't change, you can call functions and know that only their return value
matters—they don't change anything outside themselves. But this makes many
real-world applications difficult: how do you write to a database, or to the
screen?"

"This design has many nice side effects. For example, testing the functional
pieces is very easy, and it often naturally allows isolated testing with no
test doubles. It also leads to an imperative shell with few conditionals,
making reasoning about the program's state over time much easier."

[1]
[https://www.destroyallsoftware.com/talks/boundaries](https://www.destroyallsoftware.com/talks/boundaries)

[2]
[https://www.destroyallsoftware.com/screencasts/catalog/funct...](https://www.destroyallsoftware.com/screencasts/catalog/functional-
core-imperative-shell)

~~~
hota_mazi
I understand the value of referential transparency and how it makes "certain"
things easy, but saying that it automatically makes testing functional code
easy is a myth. Sometimes it does, sometimes it doesn't.

If you want to stick to referential transparency, you can't use dependency
injection: you have to pass all the parameters the function needs. None of
these can be implicit or belong to a field on the class since that would mean
side effects. The `Reader` monad is not dependency injection, it's dependency
passing and it comes with a lot of unpleasant effects on your code.

And because of that, functional code is often very tedious to test. Actually,
in my experience, there is a clear tension between code that's referentially
transparent and code that's easily testable. In practice, you have to pick
one, you can't have both.

~~~
bad_user
First of all, passing parameters in functions is "dependency injection". And
what you're describing is a _really good thing_.

Lets be honest, most dependency injection frameworks and techniques are about
hiding junk under the rug. But they fix the symptoms, not the disease. You
see, if you find yourself having components with too many dependencies,
feeling pain on initialization, the problem is that you have too many
dependencies, which actually means you have too much tight coupling and not
that it is hard to initialize them. At this point you should embrace that pain
and treat the actual disease.

Also, functional programming naturally leads to building descriptions of what
you want, in a declarative way. So instead of depending directly on services
that trigger side-effects directly, like a DB component that does inserts or
something doing HTTP requests or whatever, instead you build your application
to _trigger events_ that will eventually be linked to those side-effects
triggering services.

There are multiple ways of doing this. For example you could have a channel /
queue of messages, with listeners waiting for events on that queue. And TFA
actually speaks about the Free monad. Well the Free monad is about separating
the business logic from the needed side-effects, the idea being to describe
your business logic in a pure way and then build an interpreter that will go
over the resulting signals and trigger whatever effects you want. There's no
dependency injection needed anymore, because you achieve decoupling.

> _And because of that, functional code is often very tedious to test._

That hasn't been my experience at all, quite the contrary, we've had really
good results and we're doing such a good job of pushing the side-effects at
the edge that we no longer care to unit-test side-effecting code. And yes, I
believe you've had that experience, but I think it happens often with people
new to FP that try and shoehorn their experience into the new paradigm.

E.g. do you need a component that needs to take input from a database? No, it
doesn't have to depend on your database component at all. Do you need a
component that has to insert stuff into the database? No, it doesn't have to
depend on your database component at all. Etc.

~~~
hota_mazi
> First of all, passing parameters in functions is "dependency injection". And
> what you're describing is a really good thing.

It's only injection if the parameter is passed automatically by a framework.
Otherwise, it's parameter passing.

And it's only a good thing if you value referential transparency over ease of
testing and encapsulation. Not everybody does (and personally, sometimes I do
and sometimes I don't).

~~~
20andup
"passing parameters in functions is 'dependency injection'"

No, its not.

~~~
hota_mazi
You're agreeing with me.

I was saying that passing parameters to functions is "dependency passing", not
"dependency injection".

------
bad_user
> _Free monads permit unlimited introspection and transformation of the
> structure of your program; Free monads allow minimal specification of each
> semantic layer, since performance can be optimized via analysis and
> transformation._

That is not true and this overselling of the Free monad is hurting the
concept.

The Free monad is nothing more than the flatMap/bind operation, specified as a
data-structure, much like how a binary-search tree describes binary search.
And this means an imposed ordering of operations and loss of information due
to computations being suspended by means of functions.

You see, if the statement I'm disagreeing with would be true, then you'd be
able to build something like .NET LINQ on top of Free. But you can't.

~~~
rtpg
Maybe this is a bit trite, but isn't the ordering given in Free simply the
order of the code? At what stage does loss of information happen.

~~~
mbrock
Yeah, the free monad structures involve lots of lambdas. It's not so much that
information is "lost", more that you cannot see beyond the next lambda
abstraction.

From a DSL perspective, it's like you can only inspect the program so far as
to know the next statement in the "do" block. To see what the next statement
will be, you need to actually evaluate the current one.

Instead of free monads, if you want very analyzable structures, look at free
applicative functors.

"Applicative functors are a generalisation of monads. Both allow the
expression of effectful computations into an otherwise pure language, like
Haskell. Applicative functors are to be preferred to monads when the structure
of a computation is fixed a priori. That makes it possible to perform certain
kinds of static analysis on applicative values. We define a notion of free
applicative functor, prove that it satisfies the appropriate laws, and that
the construction is left adjoint to a suitable forgetful functor. We show how
free applicative functors can be used to implement embedded DSLs which can be
statically analysed."

[http://arxiv.org/abs/1403.0749](http://arxiv.org/abs/1403.0749)

~~~
lmm
The problem is that applicative functors aren't powerful enough to allow
computations that depend on previous results. I suspect what we want is
something like free ArrowChoice, but I'm not aware of any work in that
direction.

~~~
jfoutz
I might be completely missing your point (my apologies if i am).

applicative is for specific elements of the structure. It's totally reasonable
to want access to nearby values, but that requires being a little bit tricky.
You could do something like a window of averages

    
    
        windows = map (take 5) tails $ [1,2,3,4,5,6]
    

and then do your fmap across that

    
    
        fmap sum windows
    

For access to prior values, you need something that looks a lot more like a
fold. In that kind of case, i'd point at traversable.

    
    
        mapAccumL (\val accumulator -> val + accumulator) 0 [1,2,3,4,5]
    

which would gather up the sums, [1, 3, 6, 10, 15]

of course, accumulator can be as fancy as you want, hashmap, set, tree, or
whatever. If you can formulate a dynamic programming solution, there's
generally a way to stuff that into traversable.

This is sort of off the top of my head and my haskell is a bit rusty, so this
probably won't compile. But i think it captures the essence.

~~~
lmm
The trouble is that applicative doesn't let you use the result of one effect
to compute the next effect. (Indeed sometimes there is no there there: Const
is a valid applicative). Monads have bind, but that's a little too powerful to
implement efficiently. Like I said, I suspect ArrowChoice or the like might be
the missing intermediate construct.

------
gravypod
I'd not say this is a feature of functional programming. This is a feature of
Object Oriented elements being included in fp languages.

These are the same things we were going to happen when using Java and C++
years ago.

You can even see similar graphics here:
[https://docs.oracle.com/javase/tutorial/java/concepts/object...](https://docs.oracle.com/javase/tutorial/java/concepts/object.html)

I remember there was another in this tutorial that shared more with the image
in this post. Although this is the same idea. You're just hiding Objects in
Objects.

~~~
lmm
The difference is that a "layer interpreter" in functional style is just a
pure function that transforms values to values, so it's very easy to test in a
very direct way. And we have laws that guarantee that composition works
correctly (i.e. the interpretation of a composition is the composition of the
interpretations). Whereas it's a lot fiddlier to confirm that an object that
uses other objects behaves correctly (you have to mock the inner objects), and
virtually impossible to determine whether composition behaves correctly since
the object's internal state is opaque.

~~~
mickronome
Thank you for writing 'functional style' and not 'functional language'. While
the latter is usually, and quite naturally, better at expressing the former,
there is usually a lot to gain simply by adapting the style to the issue at
hand, which not _necessarily_ implies changing languages.

What follows is not necessarily of high value, I'm simply a working programmer
since 20 odd years that's bit weary and sad that the craft appears to be stuck
in a rut by getting stuck between an unnecessarily theory-less reality and a
nirvana of unrealistic purity. It's - maybe - a backdrop to explain why I felt
a need to thank you for your choice of words with so many words.

If we consider every problem has a shape (loose analogue for the set of
constraints thet uniquely identifies a problem) , then for every shape, or
class of shapes a problem embodies, there exists an in some sense - ideal -
language to solve that problem. Few are however the times when you only need
to solve one discrete class of problem in the same system, but in case of
mismatc between language and shape, it's quite common the only solution
brought forth is to change languages. Unfortunately that solution is rarely
feasible for a multitude of reasons. In the fallout after having to keep
working with the same non-ideal language, the entire idea that there are
multiple ways - styles - to express a solution is sadly often lost. This would
not necessarily happen if the idea that style matters enough that when we
can't change language, we could, and would still change how we express the
solution within our constraintd. Be it in any language or paradigm under the
sun, we need the words from them all to be able to talk about our problems, as
they are either unique or already solved.

~~~
lmm
The functional mindset views programming languages almost as families or
toolkits: solutions should be written in the language of the domain, and
successively interpreted to the language of the machine. Thus you don't change
language to adapt to a different domain; rather you write a new domain
sublanguage. The challenge is if anything to avoid going too far in the other
direction; lisp in particular is notorious for being so flexible that no two
people's lisp styles end up compatible.

I think there's a happy medium to be found. As my programming career has
progressed I've become more and more in favour of Scala for everything - I
think it gets pretty close to striking the right balance between the
flexibility to express any given domain and the consistency to allow
programmers to collaborate.

------
jackmott
I didn't understand a word, and I bet 99% of people who clicked the link
didn't either.

~~~
carapace
And there's the rub. I don't doubt this stuff is useful, and I'd like to know
more. But it seems really hard to bring this stuff "down from the mountain" in
a way that makes it easier to understand and utilize for us mere mortals.

~~~
koloron
Here are two blog posts on free monads that should be accessible for people
who understand what a monad is and are able to read Haskell:

[http://www.haskellforall.com/2012/06/you-could-have-
invented...](http://www.haskellforall.com/2012/06/you-could-have-invented-
free-monads.html)

[http://www.haskellforall.com/2012/07/purify-code-using-
free-...](http://www.haskellforall.com/2012/07/purify-code-using-free-
monads.html)

------
tucaz
Unrelated to functional programming there is Onion Architecture defined by
Jeffrey Palermo in 2008 which I find a very nice and simple way to
architect/organize a LoB application.

[http://jeffreypalermo.com/blog/the-onion-architecture-
part-1...](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/)
[http://jeffreypalermo.com/blog/the-onion-architecture-
part-2...](http://jeffreypalermo.com/blog/the-onion-architecture-part-2/)
[http://jeffreypalermo.com/blog/the-onion-architecture-
part-3...](http://jeffreypalermo.com/blog/the-onion-architecture-part-3/)
[http://jeffreypalermo.com/blog/onion-architecture-
part-4-aft...](http://jeffreypalermo.com/blog/onion-architecture-part-4-after-
four-years/)

------
munro
How does this architecture constrast with Rust? Has anyone built anything like
this in Rust?

It makes me think of an article where the author tries to abstract the
implementation of IO from the the domain logic. [1]

[1] [https://blog.skcript.com/asynchronous-io-in-
rust-36b623e7b96...](https://blog.skcript.com/asynchronous-io-in-
rust-36b623e7b965)

