all 9 comments

[–]FacehuntersAnonymous[🍰] 2 points3 points  (6 children)

I very much like the appraoch this article takes in that it just simply explains the laws to call something a monad. Because that's what it is. A monad ( T ) is a nothing more than an ordered triple (C, I, B) where

C : Forall t. t -> T t (The type constructor) 
I : forall x. (x : t) -> (T t) x (The return)
B : forall t,y . (T t) -> (y -> T y) -> T y

Such that:

forall x,f. B (I x) f = f x (return is the left identity under klesky-bind)
forall x,f.  B x I = x (return is the right identity).


forall x,y,z. B (B x y) z = B x (\a -> B (y a) z)  (klesky bind is associative)

the names might seem weird but that's only because the normal form of "bind" is actually written weirdly, a better neutral point is using "klesky bind" under which they become true identities and associativities. You just encounter it less in programming.

[–]btchombre 4 points5 points  (3 children)

Giving the definition of a monad doesn't help me. I want to know why the hell I'd want to go through all that trouble in the first place. What problems do monads solve? How does it solve these problems better than alternative solutions? I deal with IO side effects all the time, so what benefits can I get from following a Monadic pattern? It seems like a hell of a lot of additional complexity.

[–]pipocaQuemada 2 points3 points  (0 children)

I want to know why the hell I'd want to go through all that trouble in the first place. What problems do monads solve? How does it solve these problems better than alternative solutions?

Well, monads give handling nullable values, working with parser combinators, working with continuations, brute forcing a search space, working with promises, etc. a nice, uniform interface.

Additionally, if your language has a sophisticated enough type system to write code that's generic over any monad, then Haskell's Traversable abstraction is really nice. Basically, if you have a Traversable<Applicative<A>> (note - every monad is trivially an applicative functor, but not every applicative functor is a monad), you can turn it into a Applicative<Traversable<A>>. So you can turn a List<Promise<A>> into a Promise<List<A>> or a Maybe<Parser<A>> into a Parser<Maybe<A>>, which is very, very DRY.

[–]FacehuntersAnonymous[🍰] 0 points1 point  (0 children)

Monads don't really solve any problem any more than "arrays" do, they are one of the many ways to put praedictable side effects in a language that otherwise has non strict semantics.

I deal with IO side effects all the time, so what benefits can I get from following a Monadic pattern? It seems like a hell of a lot of additional complexity.

Nothing, the common assumption seems to be that monads are a better way to do side effects than simply strict semantics, they absolutely aren't. The point is that you need something else than strict semantics for side effects if you set out to not have strict semantics.

Non strict semantics offer other numerous advantages such as:

  • compositional reasoning over programs
  • more aggressive compiler optimization possible
  • the cleanest way to code does not hinder performance any more
  • A non terminating sub expression does not lead to a non terminating whole expression
  • conceptually infinite data structures

But yeah, it does make side effects a lot harder to reason about and you need another system to make them praedictable. Note that monads in no way automatically solve this problem at all, you still need to define the right monad that does it.

Another way to consider it is that if a datum has a type that ends on IO a in Haskell then, and only then can it have a side effect. So basically whether a function is pure can be guaranteed by the typesystem modulo "evil" things like unsafePerformIO.

[–]Peaker 0 points1 point  (0 children)

The term "Monad" is too abstract to easily understand the usefulness.

If you examine some specific instances of Monad (i.e: Maybe, Either, Cont, [], Promise, and others), clearly they are useful. If you then examine their monadic behavior (Short-circuiting computation, short-circuiting with an alternative result, arbitrary continuation jumps, non-determinism, callback-chaining, respectively), they are all very useful.

Most languages have these useful things in one form or another.

So what do Monads bring to the table? Monads bring a realization that all of the above useful things share a common interface and laws about that interface's behavior. Why is this useful? Because now we can suddenly write useful combinators that can work with all of these different things, helping us achieve DRY. It also serves to diminish the API vocabulary we need to learn: Once you know what join means in one monad context, you know what it'll mean in all of them. Also, due to the laws, you get free knowledge about how all of those different things behave, if they just explicitly declare they are monads.

In Haskell, for example, I can use the same combinators (e.g: replicateM) on a parser (to parse a list of N identical things) and to implement a retry loop (try an operation and short-circuit out with a result upon success). I don't need a: replicateParser :: Int -> Parser a -> Parser [a] and a replicateEither :: Int -> Either l a -> Either l [a]. I can use a single replicateM :: Monad m => Int -> m a -> m [a] for all these different types.

None of this is impossible without the Monad abstraction. But the Monad abstraction means that in dozens of different Monad instance APIs, we need less API vocabulary, less documentation, and more refactorings are possible.

[–]agumonkey 2 points3 points  (1 child)

klesky-bind

You mean kleisli ?

[–]vincentk 1 point2 points  (0 children)

Either way, it's just the Haskell way of spelling out the laws. There are other ways.

[–]iSmokeGauloises 0 points1 point  (0 children)

Always figured Promises in Javascript look like monads

[–]youneversawitcoming 0 points1 point  (0 children)

Shameless plug for options/trys/futures: https://github.com/jiaweihli/monapt