Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon. Entire thread

Monads

Name: Anonymous 2010-12-28 10:37

WTF are they and should I care?

Name: Anonymous 2010-12-28 12:31

>>1
Consider a list of elements. It would be nice to have a function like List<R> map<T, R>(List<T> source, Func<T, R> f), which takes a List of T, applies the supplied function to each element, and returns the list of result.

Consider a type, instances of which can hold an element or no elements. For such a type it would be useful to have a function like Nullable<R> map<T, R>(Nullable<T> source, Func<T, R> f), which either returns a new Nullable containing the result of function application, or a Nullable<R>.Empty if the source nullable is empty.

Consider a type representing a delayed computation. It too should have a function like Future<R> map<T, R>(Future<T> source, Func<T, R> f), which passes this function to a separate thread, which then adds it to the list of transformation it has to perform.

Do you see a pattern? It is called Functor (not to be confused with C++ "functors"). A Functor is a function like that (it's usually called "bind", because it binds the argument of the given function to the items contained within) plus the constructor that constructs an instance of a functor from a single object of the base type.

Note that being a Functor is like supporting an interface -- it says what you can do with the stuff, not what you can't do. For example, our Future<T> would also support the T force<T>(Future<T> source) method, which blocks execution of the current thread until the worker thread produces the result and returns it. And so on -- it's like IComparable -- it says that you are allowed to compare stuff, but doesn't prevent you from doing whatever else you might want.

Also note that you can't properly implement Functor as a real interface in languages like C# or Java because they lack higher-order generics (you want something like IFunctor<TFunctor<TItem>>).

Finally, note that a proper functor obeys some simple laws: if you have a valid chain f(g(x)), you can wrap it in your functor at any point, starting with x, or with g(x), or with f(g(x)), and get exactly the same resulting functor. In other words, functors must preserve function application.

For example, a functor on integers which increment the value each time bind is called, is not a proper functor.

OK, now observe the following expression

var x = Nullable(10).bind(x => Nullable(20).bind(y => x + y))

If we can get it to work, then we can perform arbitrary calculations in our functors! But oh no, instead of Nullable(30) it returns Nullable(Nullable(30))! What do?

Monad<R> bind<T, R>(Monad<T> source, Func<T, Monad<R>> f)

BAM! If we require the function to wrap the value in our, well, it's Monad now!, it can decide to not wrap it excessively, and our code now works!

That is basically all.

Why should you care? Well, why should you care about the Visitor pattern for example? You have a problem, you look at it and suddenly realize that the solution should be an implementation of a Visitor pattern -- now you know how to call your functions, what properties they should have, what to write in comments so that some other programmer could immediately understand the purpose of the code, etc. It doesn't even matter if your language supports double dispatch natively -- it's about the structure of the code.

Functors and Monads are much more pervasive than the lesser patterns like Visitor, so it pays to be able to recognize them in your code.

Also, this trick we did when switching from Functor to Monad is nice and occasionally useful -- for example, I was writing an html simplifier recently, and suddenly discovered that the code gets much cleaner if instead of a single node each of my functions returns a list of nodes, to be integrated in the parent node.

Newer Posts
Don't change these.
Name: Email:
Entire Thread Thread List