Hacker News new | ask | show | jobs
by Hirrolot 1161 days ago
I find the terminology that Haskell uses quite misleading for software engineering. It borrows concepts from category theory with quaint names such as a "monad", "endofunctor", "catamorphism", etc. The problem is that, instead of a "monad", we can say "brrrdogcogfog" and nothing will change -- the name is absolutely irrelevant to the problem being solved. Given that a monad is an interface for sequential computation, a much better name would be something like "Seq", "SeqComp", or something like that.
8 comments

> Given that a monad is an interface for sequential computation, a much better name would be something like "Seq", "SeqComp", or something like that.

Just because you can look at something as describing a computation doesn't mean you always should. For example:

    data BinaryTree x = 
        | Leaf x
        | Node (BinaryTree x) (BinaryTree x)

    instance Monad BinaryTree where

        return :: a -> BinaryTree a
        return x = Leaf x

        bind :: (a -> BinaryTree b) -> BinaryTree a -> BinaryTree b
        bind f (Leaf x) = f x // replace a leaf with the result of calling f on its label
        bind f (Node l r) = Node (bind f l) (bind f r) // traverse down the tree, ultimately replacing all the leaf nodes with a new subtree
You can choose to interpret a binary tree as describing nondeterministic computation where you have two choices at every step, but I rarely do. Most of the time trees are just trees.
The tree is a tree, but the Monad instance is sequencing modifications to the tree.
Sure, Kleisli arrows `a -> m b` are generally best interpreted in a computational sense. But with something like `IO`, the actual objects `m b` are computations as well, and this intuition is not as broadly applicable.
As I understand it, monads help solve the problem of sequential computation in Haskell but the concept is not limited to that. For example, how would you consider the monadic properties of data type like Maybe or Either to be (exclusively?) interpreted through the lens of sequential computation? What about commutative monads where the order doesn’t matter?
The `Monad` instance for `Maybe` and `Either` is precisely for doing sequential computation!

Consider the following:

    myBigSubroutine :: Maybe Int -> Maybe Int -> Maybe Bool
    myBigSubroutine ma mb = do
      a <- ma
      b <- mb
      return (a > b)
Here we are sequencing the "effect" of optionality. `ma` must be evaluated before `mb` and if it returns a `Nothing` then we short circuit and do not evaluate `mb`.
I think the commenter who mentioned syntax is on to something. If I write this

    fn my_big_subroutine(ma: Option<isize>, mb: Option<isize>) -> Option<bool> {
        match (ma, mb) {
            (Some(a), Some(b)) => Some(a > b),
            (_, _) => None,
        }
    }
it’s clearer to me what the intent is. I’m not sure why the other syntax is so hard for me but it feels hard to understand for some reason.
The `do` block I showed is more like this:

    fn my_big_subroutine(ma: Option<isize>, mb: Option<isize>) -> Option<bool> {
        match (ma) {
            (Some(a)) => 
              match (mb) {
                (Some(b)) => Some(a > b),
                _ => None
              }
            (_) => None,
        }
    }
Which in this case is equivalent in this example, however I'm trying to stress the sequencing. Imagine `mb` had some very expensive computation in it, then it will remain an unevaluated thunk if we shortcircuit on `ma`.

> it’s clearer to me what the intent is. I’m not sure why the other syntax is so hard for me but it feels hard to understand for some reason.

We can write `myBigSubroutine` with case matching:

    case ma of
      Nothing -> Nothing
      Just a ->
        case mb of
          Nothing -> Nothing
          Just b -> Just (a > b)
In fact, the `do` notation version desugars to something equivalent to this snippet.

The motivation for using the `Monad` instance (and thus `do` notation), is that it allows us to be polymorphic over the effect described by `>>=` (and thus `do`).

This lets us have a customized version of sequencing computations specialized to whatever "effect" we need, not just casing on optional values.

> `ma` must be evaluated before `mb`

No - either one can be evaluated first, with the other being short-circuited. If you swap the order of those lines, the function is exactly the same (in terms of inputs and outputs, at least).

This `do` syntax desugars to binds like:

    ma >>= \a -> mb >>= \b -> return (a > b)
We can then inline the definition of `>>=` and `return` to get:

    case ma of
      Nothing -> Nothing
      Just a ->
        case mb of
          Nothing -> Nothing
          Just b -> Just (a > b)
Imagine that `mb` is actually a really expensive computation that we don't want to perform unless `ma` returns a value. Sequencing our case statements in this way allows us to do that. `mb` will remain an unevaluated thunk until `ma` evaluates to a `Just a` value.
monads help solve the problem of sequential computation in Haskell

It probably confuses people because this is a problem haskell created for itself.

F# calls them Computation Expressions which is far more approachable imo
That sounds almost as vague to me as “object” does in OOP. Don’t non-monadic functions also consist of expressions that describe computations?
Everything is vague. Hell, Haskell functions are not really functions in the mathematical sense.
To me, “computation expression” is significantly more vague than “function” is in Haskell. “Seq” as was suggested by someone else here seems clearer. But I would genuinely be happy to understand the F# point of view on this better.
> Given that a monad is an interface for sequential computation

And what the hell is an interface for sequential computation? I think I understand what these Maybe types are and what they accomplish but "interface for sequential computation" sounds a lot like those buzzwords people mix together that could mean anything.

It's an API for ensuring work gets done in a specified order.

Like opening the jar before sticking a knife into the peanut butter.

Yeah, well, so is every programming language ever created. When I write any code at all, it's to ensure the instructions are laid out in the correct order for the computer to execute.
Composition/SeqComp comes for free. A monad is how to wrap something up,apply a function to the wrapped up thing, and how to unwrap it.
Composition is not sequential in a pure, lazy language with a graph reduction runtime like Haskell.
A monad is just a monoid in the category of endofunctors. Would most people know what "brrrdogcogfog" means? Couldn't we make that argument about literally any word? I don't see why it applies more here than elsewhere. For people who have experienced it before, it's straightforward and easy to work with, for those who haven't, then there's a learning curve. No one would likely have encountered "brrrdogcogfog" before and everyone would have to go through the learning curve.
Like a Javascript ArrayBuffer? No, a Stream?
tl;dr -> I agree that the terminology is probably not something that enhances cohesion amongst devs using Haskell, and certainly can be distracting in a pedagogical setting.

I think the fact your experience leads you to believe that a monad is an interface for sequential computation. A monad is often used for ordering computations, but Haskell’s monads can also be commutative (like the Reader instance of monad) which do not order anything.

The real issue is that the naming convention where some typeclass is named after a concept in category theory means wildly different things to different developers. For instance, I would expect a type/typeclass named for some categorical construct to behave in the way the categorical construct behaves and that would be the extent of what I use it for. However, some developer may see a particular usage the same construct and extrapolate that said construct is intrinsically tied to that algorithmic pattern of usage.

So the problem is controlling expectation and managing consistency throughout the dev community. I doubt Haskell will ever get away from the category theory inspired libraries and the subsequent naming conventions. See the relatively lively development of the profunctor optics based work. But, I can certainly see how it may distract or confuse newcomers.