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

Honest beginner question: Why wouldn't I just unwrap the values first and then do computation with the unwrapped values?

I get how Maybe types can be useful for parsing something or doing IO but once you get your Maybe value, wouldn't be the first action to unwrap it? If it is Nothing you can either provide a default value or just stop right there. I don't see the sense of dragging the Nothing further done with you and having to deal with potential nothingness further done the stack even if you have the tools to make it look nice.

Maybe there are other functors where this makes more practical sense?



> I get how Maybe types can be useful for parsing something or doing IO but once you get your Maybe value, wouldn't be the first action to unwrap it? If it is Nothing you can either provide a default value or just stop right there. I don't see the sense of dragging the Nothing further …

I agree that this technique works well — as long as you need to use only one Maybe at a time. As soon as you want to combine multiple computations, each of which returns a Maybe, this quickly becomes horrible and cumbersome:

    case parseNumber str0 of
      Nothing -> Left "parse error"
      Just str1 ->
        case parseChar '=' str1 of
          Nothing -> Left "parse error"
          Just str2 ->
            case parseNumber str2 of
              Nothing -> Left "parse error"
              Just str3 -> …
Monads are, quite simply, a way of abstracting this repeated structure.

And of course, then you get some data structures which can’t be unwrapped at all. The standard example is Promises as seen in e.g. JavaScript, but there are others.


"Monads are, quite simply, a way of abstracting this repeated structure."

More precisely, the monad interface allows abstracting this particular repeated structure in much the same way an iterator allows abstracting the repeated process of "get the next thing", but monad does not exist for Maybe just as iterators do not exist for arrays.

It's easy to make the mistake of focusing on the Maybe portion of that repeated code snippet, but the value of the monad interface in that code snippet is everything other than the Maybe portion, that pattern of descending into a computation over and over again with a closure, that fits into a number of data structures and an even larger number of data structures that can be coerced into fitting into that pattern without much effort.

Moreover, Maybe is a bad way of understanding the monad interface and pattern because it does not fully use the monad interface. It doesn't take advantage of the way the Maybe value is passed through to thread any additional values through. While a valid example of a monad interface usage, it is also a degenerate one that makes for a difficult lens to understand the interface.


I'm a big fan of trees and promises as more complete representatives of monads. Trees give us free monads, where `join` grafts the child tree contained in a leaf onto the parent tree; and promises are a bit more viscerally monadic, as you really can't get at the value in a promise, since it probably isn't even there yet.


Too true. I used Maybe as an example only because it had already been mentioned in the parent comment, but I agree that it doesn’t get across how incredibly useful monads are.


One benefit to keeping your value wrapped in a Maybe is that as you transform and manipulate the value and pass it around in your system, you leave it up to the last place in your system that uses the value to define the fallback value in the case of a None rather than defining a fallback value part way through and establish a convention that the fallback value means nothing was found at some other part of your system.

Another benefit to using Maybes is that you avoid the rigamarole of null checks at every call site where you want to use the value. If you have a function that returns null or a value, whenever you call that function you'll always have to add an if guard to validate it's not null. If it is, that function itself may return null, and callers to it will again have to implement the same check.

I wrote a JS implementation of the Option object and the readme has lots of specific examples about these benefits: https://github.com/sbernheim4/excoptional and a few blog posts on these ideas[0][1]

[0] https://sambernheim.com/#/blog/the-engineers-schrodingers-ca... [1] https://sambernheim.com/#/blog/building-a-monad


The unwrapping is implicit so you can pretend it's not there until you absolutely need it. To take a related example from c# (please excuse my pseudo c#, I'm rusty) you can use the operator ?. to say:

    return myList?.deleteFirst?.takeFirst?.name
you could also say:

    if (myList != null){
        firstDeleted = myList.deleteFirst;
        if (firstDeleted != null){
            newFirst = myList.takeFirst;
            if( newFirst != null){
                return  name;
            }  
        } 
    }
but I find the first one much easier to think about.


> Why wouldn't I just unwrap the values first and then do computation with the unwrapped values?

Same reason why you iterate over an array instead of assigning each value to a temporary variable: you have less code.


Plus you can do it also for indeterminate size iterators, or values not there yet (promises etc)...


you can unwrap. But a monad allows the monad implementor to treat the sequence of transformations applied to a monad as a first class object that can be manipulated.

For example for the list monad the transformation could be applied in parallel or not at all if the list is only partially consumed; for the IO monad you could perform every operation asynchronously in an event loop instead of blocking.


In general, the monad use case doesn't assume that there's the possibility of "unwrapping". For Maybe it's fine and can be used to replicate monadic functionality. But there are other functors/monads, as you suggest, which make less sense.

Here's a pretty odd one:

    data Needs r a = Needs { execute :: (a -> r) -> r }
In other words, a value x :: Needs Int String is a computation which can return an Int if you provide it a means to convert a String into an Int. You might conceptually say that it will give us a "complex" or composite "view" into values of type String given a "simple" or "atomic" view. For instance, length

    (execute x) length :: Int
This type seems as though it almost certainly "contains" a value of type a (or String in the concrete example above). It might actually contain many such values. Here are three examples, the first two are the cases mentioned above. The third is similar to the None case with Maybes.

    ex1 :: Needs (\view -> view "Hello, world")           -- contains one string
    ex2 :: Needs (\view -> view "Hello, " + view "world") -- contains two strings
    ex3 :: Needs (\view -> 0)                             -- contains no strings
We could also imagine more exotic manipulations occurring inside of the Needs. For example

    ex4 :: Needs (\view -> let n = view "Hello" in view (repeat n "world, ") \* n + 10)
It should also be clear that there's no way to "unwrap" a value of type Needs R A. This is obviously true if R and A are different types. You actually could imagine using mutable state, like a mutable vector v, to view all the As stored inside of a Needs

    (execute ex4) (\a -> { push v a; () }) -- not really Haskell syntax
But this throws away all of that repeat and * and + nonsense in ex4. It's not really the same as a true "unwrap".

But Needs is still a monad.

    pure :: a -> Needs r a
    pure a = Needs (\view -> view a)

    (>>=) :: Needs r a -> (a -> Needs r b) -> Needs r b
    m >>= f = Needs (\view -> execute m (\a -> execute (f a) view))
So, Needs is an example of a Monad which cannot in general be unwrapped. Working with it is a little complex in most cases, but the monad operations help to consolidate a couple very common, useful operations on Needs.

Finally, I'll give up the ghost and note that Needs is known as the Continuation-Passing Monad. It helps you write code in CPS style without having to actually, deliberately pass continuations all around. This can be really useful for a few dumb-programmer tricks, but it's also a pretty useful framework for building embedded domain-specific languages.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: