Hacker News new | ask | show | jobs
by tome 3247 days ago
It's obvious that the concept of monad can either be explained in 5-10 lines in both JavaScript and Haskell, or neither. Which are you claiming?
1 comments

It's not obvious.

I'm claiming that:

- the concept of monad can be explained in 5-10 lines in Javascript (demonstrable)

- the concept of a monad requires multiple years and tens of tutorials in Haskell (also demonstrable)

I just want to reiterate: point 1 is totally false. That description you linked is incredibly incorrect, captures almost nothing of the spirit of what a monad is, and is somewhat disingenuous.

Lots of people get excited about "monads" and then rush out to write tutorials to try and capture whatever mental model they're using. These mental models may arrive at correct results most of the time, but they're often not really transferrable to another human.

Learn You A Haskell takes a different approach, in which you arrive at creating monads because you naturally derive them as a way to deal with the tedium of functional code w/out such mechanisms.

"Monad tutorials" are becoming much less frequent now that such approaches are offered. Everyone just says, "Go read this chapter or two of this freely available book and you're good to go."

You know, just like any major feature in javascript.

Demonstrate a pair of tutorials aimed at the same level of reader, one in JS, and one in Haskell, where you end up with a better understanding of the idea of a monad reading the former (as opposed to the implementation of the Monad instance for a list).
TIL there's some "idea of a monad".

Basically this is (in my mind) what's wrong with Haskell: it's overly concerned with the Platonic ideal.

Meanwhile that one page on jargon has shown me that I effortlessly implement any and all of those things daily (and understanding what I'm doing) without the need to understand "an idea". I just use the tool that solves the problem. If someone insists on calling this "monadic composition", or "lifting over typeclasses", or "zygohistomorphic prepromorphisms", so be it.

It's code reuse for concepts. Wouldn't you agree that reusing intuition about things is good? It's the same as knowing what big-O is instead of just memorizing "bubble sort is slower than insertion sort, insertion sort is sometimes faster than quicksort but usually not", or knowing what concurrency is instead of memorizing the API of a library in your favorite language.
The entire "concept" of a monad fits in that description I linked to. It can be easily reused (which I've done numerous times with it, and with other concepts on that page).

Haskell for some reasons insists that I should only go for "The concept of a monad, which arises from category theory, has been applied by Moggi to structure the denotational semantics of programming languages" and

  A monad is a triple (M,unit,⋆) consisting of a type constructor M and two operations of the given 
  polymorphic types. These operations must satisfy three laws given in Section 3.
  
  We will often write expressions in the form
    m ⋆ λa. n
Should I? Really?
Yes, you should care about the laws!

They allow you to edit code without the aforementioned released-last-week test runner having to check all your code after a big refactor. I mean, you can't call something with a "flatMap" method and a "return" method a monad! There are tons of nonsensical definitions that fit that which are going to become very unpleasant to use quickly.

> Basically this is (in my mind) what's wrong with Haskell: it's overly concerned with the Platonic ideal.

I read this and I think what's got you rustled here is that Monad is such a generic concept. It's quite higher level and so you can do novel things like write functions that don't know how they're executing, just that they are.

As an example:

    -- Config is a typeclass that enables getting a
    -- keyval from a config. The return type is MonadIO
    -- because config might need IO.
    loadTarget :: (MonadIO m, Config a) => a -> m a
    loadTarget config = do
        v <- grabKeyVal "host" config
        w <- grabKeyVal "port" config
        return (v,w)
What does that code do? The answer (if it's written carefully) is that it depends on what the underlying monad is! And that's a good thing, in many cases. If the monad is Maybe + IO, then you have a conditional loader.

But if the monad is an array and IO then you can specify many hosts and many ports and this code enumerates them all. If that's passed to a ping function like so:

    loadTarget config >>= pingHostPort

    -- alternatively

    doPings config = do
      hostPort <- loadTarget config
      pingHostPort hostPort
Well then your code will do the right thing, but a different thing, based entirely on the types alone! And you can generalize this out to even more powerful types. For example, you could write a web app that server side could do local network ports for you (why? you're a maliclious hacker of course!). In that case it might make sense to use the Continuation monad.

tl;dr and finally:

You say this is stupid abstract stuff, but the folks delivering features to you in the Javascript world disagree. You have generators now, which are a much more real and fair explanation of how to model monads in Javascript than that silly code snippet you posted that doesn't capture the spirit of them at all.

What's more, careful application of these concepts leads to libraries which are just better than anything you can have without appealing to generators. A great example of this Purescript-config. Here is an actual (redacted) same of some code I use at work in an AWS Lambda function to read the environment:

https://gist.github.com/KirinDave/9af0fc90d005164743198692f3...

So I have complete error reporting (full sets of missing keys) just by describing how to fetch the values from the environment. I can transparently switch to any other file type under the covers by changing from using "fromEnv". I only know that there is a key-value store in there.

Doing this in OO is really, really hard to get right, because imperative OO code cannot easily parameterize the execution strategy hierarchically without appealing to generics and ad-hoc polymorphism. That's hard.

The applicative style adopted here is very simple to re-interpret, because we can parameterize code on monads and applicatives (which explain the execution strategy of this code beyond its coarse structure) as we see fit.

You can do that with generators but it's frustratingly hard. Doing it in a truly generic way? Even harder. For this approach, the results are free (and Free, but hey).

I can give you other examples of how Purescript makes certain difficult aspects of javascript simply vanish, if you'd like.

Thank you for examples.

I have one complaint though:

> Doing this in OO is really, really hard to get right,

Why do you equate FP with monads? Moreover, why do you equate FP with Haskell/Purescript (statically typed FP with monads)?

The use of monads is a side-effect (ha!) of committing to purity throughout a language, and that's what FP is being equated to: pure statically-typed FP.

(You can argue about how justified that is, of course. I'm not going into that, but you might want to see what John Carmack has to say[0]. No, he doesn't end by saying "we should all convert to the church of Haskell now", but he does talk about how large-scale game programming refactors are made easier when you're working with no (or very disciplined) side effects.)

Monads are not the only way to deal with effects while keeping purity, although they were the first discovered and so on: algebraic effect systems as in Koka[1] (and as simulated in Idris or PureScript) are another alternative. Koka infers effects, so it's probably easier for a C-family programmer to pick up (I know little about it, though).

[0]: https://www.youtube.com/watch?v=1PhArSujR_A

[1]: https://www.microsoft.com/en-us/research/project/koka/