6

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?

1
  • Common Lisp has its own compiler as part of the language. Haskell has Monads.
    – Will Ness
    Commented May 30, 2019 at 12:56

4 Answers 4

12

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.

10
  • Oh, hi Eric. *whistles innocently* Commented Jul 8, 2016 at 15:43
  • 1
    @MasonWheeler: Thanks for the link. I was definitely trying to avoid rlv.zcache.co.uk/… Commented Jul 8, 2016 at 15:54
  • out of curiosity, in your blog posts about monads, you start off by implementing functor instances for the types, but you never mention the f-word. was this so as to not scare people off? I thought it was kind of a neat chance to sort of "disarm" the "scary words" by showing that they really aren't all that complicated, some of them are just pretty abstract.
    – sara
    Commented Jul 8, 2016 at 16:51
  • 1
    @kai exactly right. I specifically wanted to avoid "a monoid in the category of endofunctors" and emphasize concepts that the OOP programmer already understands: generic types, static methods, and delegate types. Commented Jul 8, 2016 at 17:07
  • 2
    @kai: Indeed, it is hard to walk the line between simplification and oversimplification; an oversimplification often introduces a falsehood. And yes there are a lot of articles about monads out there; I was specifically trying to make a series that was geared towards the pragmatic line-of-business OO programmer, which I felt was not well represented in the existing set of tutorials. Commented Jul 8, 2016 at 18:13
8

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!

1
  • 1
    bind/ApplySpecialFunction's arguments never really made much sense to me, but around the middle of the series, when he starts explaining the Haskell syntax, he includes a phrasing that helped my understanding significantly: "bind operation f onto the end of workflow m and give me the resulting new workflow"
    – Izkata
    Commented Jul 8, 2016 at 14:35
2

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)

2
  • And the prose definition in sentence two is accurate?
    – 52d6c6af
    Commented Jul 5, 2016 at 14:35
  • the second sentence looks like it is a description of unit/return rather than a monad as a whole
    – jk.
    Commented Jul 5, 2016 at 14:42
1

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.

6
  • Your discovery that there are only singleton monads, identity monads, or monads where there are values that cannot be created by unit, is an example of a "free theorem"; that is, a fact about code which can be proven by inspecting the types of the methods rather than the code which implements them. If this subject interests you, see stackoverflow.com/questions/12421085/… Commented Jul 8, 2016 at 15:48
  • Your second "This ... should be ...." isn't clear. The second fn should be something like fnM to make it clear it's not the same function. Possibly the same in other examples, but I'm still getting up to speed on monads myself.
    – Izkata
    Commented Jul 8, 2016 at 18:04
  • @Izkata: It's the same function. The haskell equivalent of that line is return x >>= f = f x. Take that and substitute notation (Monad(x) for return x, .bind(fn) for >>= f and fn(x) for f x) and you get what I had. Commented Jul 8, 2016 at 19:20
  • @MichaelShaw As someone who had some of the same misunderstandings as OP, it's written as Monad(fn(x)) with the thought that fn does not return a monad. But when you say it should be just fn(x), that only holds because that one does return a monad - and therefore is not the same function.
    – Izkata
    Commented Jul 9, 2016 at 1:00
  • @Izkata: The first one (Monad(x).bind(fn)) returns a monad as well. It has to, because it's getting used in a call to bind, which takes a function that returns a monad. fn in Monad(x).bind(fn) is the same function as fn in fn(x). Commented Jul 9, 2016 at 3:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.