Hacker News new | past | comments | ask | show | jobs | submit login
Free and Freer Monads: Putting Monads Back into Closet (okmij.org)
72 points by alphonse23 on Nov 18, 2015 | hide | past | favorite | 15 comments



The web page mentions a "Haskell Symposium 2015 paper". I didn't see it linked from the page, but I'm fairly certain it's this one:

Freer Monads, More Extensible Effects

http://okmij.org/ftp/Haskell/extensible/more.pdf



The conclusion is pretty bold:

"The ambition is for Eff [the extensible freer monad, the monad of extensible effects] to be the only monad in Haskell. Rather than defining new monads programmers will be defining new effects, that is, effect interpreters."


Eff needs some additional optimization or some extra compiler support before it can thrive in the wild, because the constant overheads are pretty severe. The asymptotic overheads though are really good, basically they managed to get rid of all possible time complexity blowups with the last Eff refinement that added type-aligned sequences.


Eff has a bigger semantic issue to deal with first.

It is only composable for commutative effects. Once you introduce non-commutative effects like throw/catch or nondeterminism, the semantics of a block of code using more than one effect depends on the order in which interpreters are run.

Monad transformers escape this issue by allowing code to specify the effect nesting order in the type. They're no panacea either, of course. But they currently allow somewhat better code reuse and abstraction properties than the Eff approach does.

If Eff ever gets the ability to control the semantics of non-commutative effects, it will have a much more compelling story.


It's not particularly worse than monad transformers with respect to non-commutative effects. With monad transformers, one can trip up just as happily on exceptions, and mtl-style stack-polymorphic code can be run with various effect orderings with possibly varying semantics.

I failed to mention though that Eff would also benefit from some boost to type inference for open sums. Currently it's throwing up hands for component effects with polymorphic inner type parameters, like `State s` or `Reader r`, so we can't do things like `put 0`, but only `put (0 :: Int)` (in the absence of other type annotations in outer scopes). It's fortunately not an impossible problem; we just need to allow for unique projections from sums in our type-level machinery (this requires some annoying boilerplate currently, but maybe GHC 8.0 will make it easier with the new kind system).


Yes, transformers allow you to get it wrong, too. But at least there is a way to get it right with them.


Perhaps there's little math, but there's an intimidating amount of Haskell jargon. Are there any lessons for people writing in other languages?


The main benefit of "free monads" and "extensible effects" is that every piece of side-effecting code comes with a convenient way to substitute your own effect handlers. For example, you could capture all I/O from a function and redirect it somewhere else, or intercept every time the function touches a global variable, etc. And you could do it cleanly, in a way that's enforced by the language. I think that's pretty exciting.


This paper is over my head, but in case anyone is wondering what Free monads are I can give a teaser for how they're useful.

Say you're automating a bio lab. You're writing software to control evacuations. The lab machinery can execute two actions (1) Alarm (sets off an evacuation alarm in an area) (2) Sterilize (burns an area with fire). Obviously we would like to call (1) for an area well before we call (2).

To these two actions we can add one of our own: (3) Wait (for some number of seconds).

Now we write a function (using Haskell types, but they're easy to read):

  evacuate :: ContaminationInfo -> IO ()
This function takes in the state of a lab (what areas have been contaminated, what areas are at risk, etc.) makes an evacuation plan, and executes it.

Now, the first things you notice is that this is a horrible function. How could we test it? We would need an actual lab (or mocks, but that's no fun).

Let's try again.

  makePlan :: ContaminationInfo -> [Action]
(Where action is either `Alarm Area`, `Sterilize Area`, or `Wait Seconds`)

This is way better! Now we can write interpreters for `[Action]`.

  testContaminationPlan :: [Action] -> TestResults

  prettyPrintContaminationPlan :: [Action] -> String

  graphContaminationPlan :: [Action] -> SVG

  runContaminationPlan :: [Action] -> IO ()
Note that all of this can be done in Python, which is pretty cool! Sadly we can't write `makePlan :: ContaminationPlan -> [Action]` in Python though. It would have to be `makePlan :: ContaminationPlan -> IO [Action]`, which means that there could be side effecting functions that slip by the programmer and aren't reflected in the return type. If one of these is Sterilize someone could have a bad day:(

Now lets say our wonderful engineers build us an excellent new action: (4) Scan (check an area for risk of contamination).

Now our [Action] plan starts to break down. Before an [Action] was very simple. An example might have been [Alarm Area1, Alarm Area2, Wait 200 Seconds, Sterilize Area1, Sterilize Area2].

But our scanner allows later actions to depend on the result of previous actions! [Action] isn't sufficient to model that -- how would our interpreters know how to thread things together (our goal is to be able to do things like start scanning an at-risk room like crazy, but not actually evacuate it unless contamination appears).

If only we had some way for our data type to reflect that some actions might need to be fed into others? Hmm, I wonder what we could use for that ...


PS Pretty much all my knowledge of this subject (and the pretty printing interpreter example in particular) comes from here: http://www.haskellforall.com/2012/06/you-could-have-invented...


This is literally textbook Design Patterns stuff. Haskell gives you executable machine-checkable design patterns, instead of just comments.


Admittedly non-illuminating, and maybe even misleading, musings:

I like to think of free monads and related constructions as dependency injection "turned inside out". With dependency injection, you configure a foozle in various ways, and then the foozle controls how and when to use the injected components. The foozle is on the outside; the dependencies, once injected, are on the inside.

With a free monad, the foozle (the "logic" of the application) is on the inside, and an outside interpreter is the one that services all the "effect requests". The foozle is "run" by the interpreter.

That's the basic idea. Then there are practical concerns of how to make it fast enough, of how to be able to handle different combinations of effects (environment reads, state, io requests...) without requiring boilerplate, and so on.


That's Inversion of Control , not Dependency Injection .


Yeahh... stay there!!! ;)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: