Question

I am trying to formulate a definition of a monad without needing mathematical terms or Haskell to understand.

Can a monad be thought of as a function that accepts a value and wraps it such that it meets specific interface and behavioral constraints that have been found to be useful when working in a functional style?

The interface constraints are:

i. It has a type constructor that defines its type (What is this?).

ii. It has a unit function that converts a value to its corresponding monadic type.

iii. It has a "bind" method that takes a monad, a function that takes a some type and returns a monad (is this another monad?), and returns a monad.

The behavioral constraints are:

i. Left identity

Monad(x).bind(fn) == Monad(fn(x)); // for all x, fn

ii. Right identity

Monad(x).bind(function(x){return x;}) == Monad(x); // for all x

iii. Associativity

Monad(x).bind(fn1).bind(fn2) == Monad(x).bind(function(x) {
  return fn2(fn1(x));
});

Is this a minimal definition of a monad?

Was it helpful?

Solution

Mason's answer is correct, and yes you should read my series. To emphasize his point more thoroughly:

Can a monad be thought of as a function that accepts a value and wraps it such that it meets specific interface and behavioral constraints

Yes, but that is not the best way to characterize a monad. You are very close though; we can make small changes to your formulation and arrive at a better characterization.

You say a monad, call it M, is a function that accepts a value and produces a wrapped value. It is the is that is problematic. Rather, a monad has such a function. A monad M is a collection of three things:

  • A transformation that takes a type and produces a new type. In C# we call such a transformation a "generic type". We have a generic type M<T>, we have a type int, and we produce a new type M<int>.

  • A generic function unit which accepts a value of type T and produces a value of type M<T>.

  • A generic function bind which accepts a value of type M<T> and a function from T to M<U>, and produces an M<U>.

That second thing is your "function which accepts a value and wraps it". It's not that a monad is that function; a monad is the combination of the monadic type constructor itself, the wrapper, and the function which binds a new function onto the end of a monadic workflow.

This is why in category theory, monads are sometimes called "triples"; because you need three things to characterize a monad.

Now, you'll note that I do the same thing as you; in my series of articles I characterize a monad as a generic type that obeys certain rules; that is, I say that the monad is the first thing, and that first thing must itself have the second and third things.

How exactly we characterize the relationships between the parts doesn't really matter; what matters is that there must be these three things: a way to "amplify" types, a way to "wrap" values, and a way to transform an instance of a monad into to another by "binding" a function onto it. If you have those three things, and they obey the obvious rules for identity, transitivity, and so on, then you have a monad.

OTHER TIPS

Eric Lippert wrote a great series on monads that actually explains them in a way that makes sense to non-Haskellers. The whole thing is worth reading, but the basic idea is as follows:

A monad is a "type enhancer" that takes a basic type and does something new to it, (such as turning a type T into a sequence like IEnumerable<T>,) with the following characteristics:

A monad is a generic type M<T> such that:

  • There is some sort of construction mechanism that takes a T and returns an M<T>. We’ve been characterizing this as a method with signature

    static M<T> CreateSimpleM<T>(T t)

  • Also there is some way of applying a function that takes the underlying type to a monad of that type. We’ve been characterizing this as a method with signature:

static M<R> ApplySpecialFunction<A, R>(M<A> monad, Func<A, M<R>> function)

Finally, both these methods must obey the monad laws, which are:

  • Applying the construction function to a given instance of the monad produces a logically identical instance of the monad.
  • Applying a function to the result of the construction function on a value, and applying that function to the value directly, produces two logically identical instances of the monad.
  • Applying to a value a first function followed by applying to the result a second function, and applying to the original value a third function that is the composition of the first and second functions, produces two logically identical instances of the monad.

If this definition seems a bit confusing, the next paragraph explains why:

Whew! And now perhaps you see why I started this series all those weeks ago with the idea of exploring the pattern by looking at examples, rather than starting in with the monad laws.

If you start at the first post and read through the series, the concept should actually make sense by the end. There's no "a monad is like a hot dog" gibberish here, thankfully!

i) its a function that takes a type and returns a new type i.e. a generic type, i'm not sure it would directly have a use in js as you don't have types

iii) would return a different type but in the same monad i.e. you can reproject from m a -> m b

it's a minimal definition however there are usually other functions defiend as well e.g. every monad is a functor so you can always define fmap for your monad also you often have a join/flatten function (bind can be implemented in terms of fmap and join)

Can a monad be thought of as a function that accepts a value and wraps it such that it meets specific interface and behavioral constraints that have been found to be useful when working in a functional style?

No. The function that wraps a value is unit.

If you're trying to see if it makes sense to explain monads as a unit function that returns an object with a bind method that follows the monad laws, it doesn't quite work. The problem is that there are monadic values that can't be created with the unit function.

For example, in the Maybe monad, the Nothing value can't be created with unit, and the whole point of the existence of the Maybe monad is to allow for the Nothing value.

In general, monadic values created by the unit function are simple and boring and plain, and don't illustrate why we wanted to have whatever monad we're dealing with in the first place. Unit is great for turning plain values into monadic values and plain functions into monadic functions for compatibility, but that's it.

After thinking about it for a bit, I was actually able to work out a proof on paper that all useful monads have monadic values that cannot be created by unit. (Specifically, either there is only one monadic value in the monad (which is useless), or the monad is isomorphic to the identity monad (which is useless in the same way the identity function is useless), or the monad has monadic values that can't be created by unit.)

This is wrong

Monad(x).bind(function(x){return x;}) == Monad(x); // for all x

it should be

unit(x) == function(x){return Monad(x);} // I'm assuming this is what you mean by Monad(x)
Mx.bind(unit) == Mx; // for all monadic values Mx

This

Monad(x).bind(fn) == Monad(fn(x)); // for all x, fn

should be

Monad(x).bind(fn) == fn(x); // for all values x and monadic functions fn

You can bind with any monadic function, which is a function of one argument that returns a monadic value of the same type that the implementation of bind works on, but you can't bind with all functions.

This is wrong

Monad(x).bind(fn1).bind(fn2) == Monad(x).bind(function(x) {
  return fn2(fn1(x));
});

it should be this

// for any monadic value Mx and any 2 monadic functions fn1 and fn2 whose types line up
Mx.bind(fn1).bind(fn2) == Mx.bind(function(x) {
  return fn1(x).bind(fn2);
});

iii. It has a "bind" method that takes a monad, a function that takes a some type and returns a monad (is this another monad?), and returns a monad.

You're using the word monad to mean several different things, which I think is fine, but is potentially unclear to people who haven't wrapped their heads around monads yet. There are three things you could mean by monad: the general pattern of monads/monads in general, a specific monad, or a monadic value. Monads in general are what you're trying to define/explain, a specific monad would be a particular instance of this general pattern/interface (like the Maybe monad or the List monad), and a monadic value is a value of the type that unit or bind output (like the list [1,2,3] for the list monad).

So we can simplify that to

iii. It has a "bind" method that takes a monadic value and a monadic function and returns a monadic value.

Although that doesn't make clear that bind doesn't work between different monads or that the type of the output of the monadic function is the same as the type of the output of the bind.

Another way to look at bind is as a monadic equivalent of function application. Plain function application takes a plain value and a plain function and returns a plain value. Monadic binding takes a monadic value and a monadic function and returns a monadic value. If you've got a monadic value Mx and a monadic function fn, you can't do fn(Mx), because fn doesn't work on monadic values. But you can do Mx.bind(fn).

i. It has a type constructor that defines its type (What is this?).

A type constructor is a concept that exists in Haskell, but doesn't really in javascript. I'm not sure of the best way to go about talking about this in javascript, but I'd probably end up talking about Classes, because even though they don't really exist in javascript, javascript programmers know what that concept is. For any monad, there has to be a definition of what monadic value means for that particular monad, and that's what goes here.

Licensed under: CC-BY-SA with attribution
scroll top