Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The whole point about monads is that they provide a uniform interface for working with values that carry some extra context. The genericity of the interface is nice, in that it allows you to write generic monad combinators, but they are only useful because of the context (effects, control flow, etc). If you don't describe any of the implementations, you are missing the point!

The best introduction to monads was written in 2006 (more than eight years ago now) and I don't imagine that a better one will be written in the near future -

http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...



One thing that this article and lot of other Monad articles gloss over is the scoping in "do" notation and when using the >>= operator.

The example:

    return 7 >>= (\x -> Writer (x+1,"inc."))
             >>= (\x -> Writer (2*x,"double."))
             >>= (\x -> Writer (x-1,"dec."))
works for this example, but a more accurate representation of how "do" notation is expanded is:

    return 7 >>= (\x -> Writer (x+1,"inc.")
             >>= (\x -> Writer (2*x,"double.")
             >>= (\x -> Writer (x-1,"dec."))))
Note that I've moved the parentheses.

Actually, you can, equivalently, omit the parentheses for the same meaning, because the '->' has very low precedence:

    return 7 >>= \x -> Writer (x+1,"inc.")
             >>= \x -> Writer (2*x,"double.")
             >>= \x -> Writer (x-1,"dec.")
The point is, most times each of subsequent functions need to "see" what was in scope in the earlier functions. Most times, the later monadic actions need information that is pulled out of the monad in previous actions.

For example, consider the following:

    main = putStrLn "Enter your first name" >>=
       \_ -> getLine >>=
       \first -> putStrLn "Enter your last name" >>=
       \_ -> getLine >>=
       \last -> putStrLn ("Hello " ++ first ++ " " ++ last ++ ".")
The last monadic action needs both "first" and "last" to both be in scope. This only works because of the way that it is implicitly parenthesized.

Until I "got" this, I was very confused by most of the Monad examples I found.

Here's an article I wrote about the IO monad which also makes this point: https://github.com/Patient0/IOMonad


I wrote this up, "So You Want To Write A Monad Tutorial in Not-Haskell": http://www.jerf.org/iri/post/2928 It focuses on all the things I've seen people miss about "monads" when they try to write an implementation in another language after the third time I saw someone claim they'd implemented "monads" in Javascript using method chaining, which I do not believe to be possible in part because of the issue you point out, which is that the scope builds up as you go.

It is perfectly reasonable to also read this as the list of elements of monadery that even tutorials written in Haskell tend to miss or fail to explain.


From your article:

> Monads are not "about effects".

What is your definition of "effects"? I ask because this article[0] and many others[1] seem[2] to use a contradictory definition, in which failure, nondeterminism, state etc. are viewed as effects and monads are a means of implementing those effects.

------

[0]: http://www.sciencedirect.com/science/article/pii/S2352220814...

[1]: http://scholar.google.com/scholar?q=algebraic+effects+monad&...

[2]: as far as I understand them; I may be wrong.


"Effect" is a more general idea. It's a lens for viewing any kind of impure computation[0]. Monads are a model for effects. You can view them as pure[1] computation in lambda calculus or as defining a region of code which, internally, has impure effects.

So to summarize: effects are a general concept, monads are a particular technology for implementing that concept.

Further complexity ensues when people start talking about the general concept of a monad which is interesting in its own right but it has a more sophisticated relationship with the concept of effects.

[0] Purity is a property of, say, functions. Its definition is a function `f` is pure if and only if

    const () . f = const ()
which usually means that non-termination is impure as well. The notion of equality you use above can finesse this definition a lot.

[1] As stated in [0], non-termination is an effect, so Haskell monads are impure in that sense. Haskell typically ignores non-termination effects, though. Generally, monads would work more or less just fine even without non-termination though. Externally you can think of them as pure.


Ironically, I added that just as I was linking it here, despite the date on that post.

In addition to tel's discussion, what I was really trying to get at is that monads aren't "about" IO effects in particular, they're not about "impurity". In this case the whole thing is pure.

Defining effects at a deep programming language research level can bring a different understanding, where all monads are about effects, but "effects" has a different meaning that most people understand.

I'll ponder how to clarify that better.


My suggestion is to use "side effects" -- it seems to me that most people have an intuitive understanding of that term.

... but I may be wrong about that.


Your C# IO monad could really benefit from using LINQ (I know you mention it at the end). If you're interested in a full set of C# monads (including a fully implemented IO monad) check my csharp-monad library [0]

[0] https://github.com/louthy/csharp-monad


The best introduction to monads that I've read is the original paper that discussed them in the context of functional programming: http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/ba...


On the same vein, SPJ's "Tacking the Awkward Squad" is also a great old paper, and focuses more on impure effects than Wadler's


If you want video, this is probably worth watching; haven't finished it yet.

https://www.youtube.com/watch?v=ZhuHCtR3xq8


For those like me who find it hard to think outside the imperative programming box, Eric Lippert's series of articles is good.

http://ericlippert.com/2013/02/21/monads-part-one/

A lot of Monad articles like to explain 'what' without first establishing 'why' (IMHO)


It's not even about the effects, really. It's simpler to think of Monad as an interface for manipulating encapsulated data in a way which keeps it encapsulated.

The simplest way to encapsulate data is to wrap it using a constructor which we keep private to our module:

    module M1 (Encap(), val1, val2)
    data Encap a = mkEncap a

    val1 :: Encap Int
    val1 = mkEncap 5

    val2 :: Encap Int
    val2 = mkEncap 10
Other modules importing M1 get access to the Encap type, val1 and val2 but not the mkEncap constructor. They can use val1 and val2 as-is, but they can't construct new Encap values or destruct existing Encap values to get at their contents. The problem is, there's not much we can do with this interface.

One way we can make this more useful is being able to apply some function to an encapsulated value. That's what Functor is for, so we can add this to M1:

    instance Functor Encap where
      fmap f (mkEncap x) = mkEncap (f x)
Now users of M1 can transform encapsulated values without being able to break the encapsulation. For example:

    val1Plus7 :: Encap Int
    val1Plus7 = fmap (+7) val1

    val2Str :: Encap String
    val2Str = fmap show val2
We're still pretty limited though, since there's no way to combine encapsulated values into new encapsulated values, or to encapsulate our own values. That's what Applicative provides, by letting us construct encapsulated values without gaining the ability to destruct them, and by allowing encapsulated functions to be applied to encapsulated values (since functions are closures, this lets us gather up encapsulated values and combine them arbitrarily):

    instance Applicative Encap where
      pure x = mkEncap x
      (mkEncap f) <*> (mkEncap x) = mkEncap (f x)
Now users of M1 can encapsulate and combine values, like this:

    -- val1 + val2
    val1PlusVal2 :: Encap Int
    val1PlusVal2 = fmap (+) val1 <*> val2

    -- New encapsulated string
    val3 :: Encap String
    val3 = pure "Hello world"

    -- val3 repeated val1 times
    val3Repeated :: Encap String
    val3Repeated = fmap rep val1 <*> val3
                   where rep n _ | n <= 0 = ""
                         rep n s          = s ++ rep (n-1) s
This is quite a powerful interface, but one thing we can't do is `collapse` double-encapsulated values into single-encapsulated values. That's what Monad provides:

    join :: Encap (Encap a) -> Encap a
    join (mkEncap (mkEncap x)) = mkEncap x
An alternative, but equivalent, definition is to allow calls to encapsulation-producing functions without encapsulating their result. Haskell's Monad is defined this way:

    instance Monad Encap where
      (>>=) :: Encap a -> (a -> Encap b) -> Encap b
      (mkEncap x) >>= f = f x
These kind of encapsulated values turns out to hold effects without breaking the language, and these interfaces turn out to be powerful enough for general computation.


>These kind of encapsulated values turns out to hold effects without breaking the language

I wonder if you're hand-waving a bit here. To me "encapsulated values" describes Identity or Maybe, but really doesn't work (IMO) for e.g. State or IO where bind is composition.


I would use the term "wrapped-up" where you've used "encapsulated", and in fact I specifically avoided talking about wrappers for this exact reason :)

Maybe my terminology could have been better, but I meant "encapsulated" in analogy to OOP, which advocates "encapsulating" all data via methods. The OOP definition of encapsulation includes using getters/setters, which is like having "wrapped-up" properties, but the idea is that we can go beyond this to calculate the data in arbitrary ways without our clients having to know about the implementation. That's what I was trying to get at here; for example, the implementation of IO involves horrible imperative yukiness, but we (the client) don't need to know that: we just use the interface, and if our functions ever get called, they will be given an appropriate argument.


This is a wonderful explanation, thank you!


I agree. I read that comment last night and could almost not sleep due to the insights it gave me. I'm going to spend the whole weekend re-reading about Functors, Applicative and Monads now. Maybe this time I'll grok it all.

Thanks Chris!


I like to rename encapsulation as invariant these days. List monad will ensure you have a list of a.


Are you sure? There are plenty useful monads which are not effects... In fact the article you linked even says so.


Yeah, I should have written "computational context" not "effects".




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

Search: