Monads
Good programming requires a lot of understanding.
As has been observed by Simon, the architecture of complexity is a layered hierarchy (or a hierarchy of layers).
The weak analogy everyone uses is of an onion, but this analogy is indeed weak. It leaves out interactions between layers and the fact that the layers are not the same, not even similar to each other.
The fundamental pattern is that there are layers - an actual abstraction barriers out there, and these abstraction barriers are penetrable for certain “structures”, which may act as “messages” or required building blocks (“parameters”).
I would say that this is the most universal and the most important pattern, and it is captures and being used literally at all levels of abstraction in slightly different forms.
Parameters of a function, an abstract interface of an ADT, a required set of interfaces of a type-class or a trait, and a set of interfaces in a module signature (SML, Ocaml) are the same notion of a /cell membrane with its “pumps” and “portals”.
The same pattern is everywhere. The confusing part is too many abstractions and wrong abstractions.
The cell membrane “pattern”
Another emergent “high level” pattern is that use of Monads for I/O “naturally” partition (and layer up) the code in a way that “pushes I/O to the outermost layer, behind an absreaction barrier”, as if to a “cell membrane”.
This is a system-level (instead of a expression-level) pattern and it is about proper partitioning of the code base, both vertical (layers of DSLs) and horizontal (modules and libraries).
Yes, a good program design (in Haskell) is very hard, but it pays off big, since a pure, declarative solution is the most pragmatic one. Just like with the math, once you properly capture the pattern it is “ethereal”.
I will explain some details below.
The Kleisli arrow.
Today I am not in the mood for going again through all the details and derivations. In short, a Kleisli Arrow is an abstraction which captures the notion of a function (morphism) which lifts its result into a monad or of crossing an abstraction barrier.
return :: Monad m => a -> m a
There is a composition operator for such “arrows”, defined as “fish” <=<
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
When we look at the types, leaving all the category theoretic bullshit aside, we may notice that we have seen this pattern before:
(.) :: (b -> c) -> (a -> b) -> a -> c
And the fundamental difference is in crossing the abstraction barrier (of some particular Monad).
A Monad in this context is just a particular instance of the Monad type-class, which defines a required interface and specifies the constraint for also being an instance of a Monoid.
The “laws” of both Monad and Monoid are informally stated elsewhere, and it is the duty of a programmer to make sure they hold.
Another fundamental property that all the composition operators (<=<
and >>=
) are implemented as nested lambdas, because there is literally no other way.
The consequence is this property that there the composition operation is implicitly properly serialized and with no way to interfere with it, could be considered as technically atomic.
The Referential Transparency property holds for both the arguments and the result of a composition. This is what keeps everything pure (only in Haskell).
Notice that once the values is “lifted” (crossed the abstraction barrier) there is absolutely no way to observe it, but it is possible to declare (specify declaratively) what to do with it eventually.
We have a reference to a m a
and can only specify what to do with it, including case analysis and pattern-matching on a value we will never observe. Again, Haskell is a declarative pure logic, not some kind of an imperative crap.
Actually, all the Haskell code is a declarative description of what eventually shall be done with values (by the runtime), and the values themeselves are never available. They just aren’t out there.
This is a no bullshit result. Have some time to really understand and internalize it. The actual purity of Haskell code (until the main
returns) is a vary big deal. It makes the Haskell code (technically) a pure logic (or pure mathematics).
Take your time.