Monad in plain English? (For the OOP programmer with no FP background)

In terms that an OOP programmer would understand (without any functional programming background), what is a monad?

What problem does it solve and what are the most common places it's used?

EDIT:

To clarify the kind of understanding I was looking for, let's say you were converting an FP application that had monads into an OOP application. What would you do to port the responsibilities of the monads to the OOP app?


UPDATE: This question was the subject of an immensely long blog series, which you can read at Monads — thanks for the great question!

In terms that an OOP programmer would understand (without any functional programming background), what is a monad?

A monad is an "amplifier" of types that obeys certain rules and which has certain operations provided.

First, what is an "amplifier of types"? By that I mean some system which lets you take a type and turn it into a more special type. For example, in C# consider Nullable<T> . This is an amplifier of types. It lets you take a type, say int , and add a new capability to that type, namely, that now it can be null when it couldn't before.

As a second example, consider IEnumerable<T> . It is an amplifier of types. It lets you take a type, say, string , and add a new capability to that type, namely, that you can now make a sequence of strings out of any number of single strings.

What are the "certain rules"? Briefly, that there is a sensible way for functions on the underlying type to work on the amplified type such that they follow the normal rules of functional composition. For example, if you have a function on integers, say

int M(int x) { return x + N(x * 2); }

then the corresponding function on Nullable<int> can make all the operators and calls in there work together "in the same way" that they did before.

(That is incredibly vague and imprecise; you asked for an explanation that didn't assume anything about knowledge of functional composition.)

What are the "operations"?

  • Return (also unit) operation — the unary operation that takes a value from a plain type and puts it into a container using the constructor — creating a monadic value. This, in essence, provides a way to take a value of an unamplified type and turn it into a value of the amplified type. Monad单元操作
  • Bind operation — binary bind operation takes as its arguments a monadic value and a function that can transform the value, and returns a new monadic value.
    Monad绑定操作
  • The bind operator unwraps the plain value, embedded in its input monadic value, and feeds it to the function.
  • The function then creates a new monadic value, that can be fed to the next bind operators composed in the pipeline.
    Monad使用管道绑定操作
  • This grants a way to transform operations on the unamplified type into operations on the amplified type, that obeys the rules of functional composition mentioned before.
  • A way to get the unamplified type back out of the amplified type. (This last point isn't strictly necessary for a monad but it is frequently the case that such an operation exists.)
  • Again, take Nullable<T> as an example. You can turn an int into a Nullable<int> with the constructor. The C# compiler takes care of most nullable "lifting" for you, but if it didn't, the lifting transformation is straightforward: an operation, say,

    int M(int x) { whatever }
    

    is transformed into

    Nullable<int> M(Nullable<int> x) 
    { 
        if (x == null) 
            return null; 
        else 
            return new Nullable<int>(whatever);
    }
    

    And turning a Nullable<int> back into an int is done with the Value property.

    It's the function transformation that is the key bit. Notice how the actual semantics of the nullable operation — that an operation on a null propagates the null — is captured in the transformation. We can generalize this. Suppose you have a function from int to int , like our original M . You can easily make that into a function that takes an int and returns a Nullable<int> because you can just run the result through the nullable constructor. Now suppose you have this higher-order method:

    Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
    {
        if (amplified == null) 
            return null;
        else
            return func(amplified.Value);
    }
    

    See what you can do with that? Any method that takes an int and returns an int , or takes an int and returns a Nullable<int> can now have the nullable semantics applied to it.

    Furthermore: suppose you have two methods

    Nullable<int> X(int q) { ... }
    Nullable<int> Y(int r) { ... }
    

    and you want to compose them:

    Nullable<int> Z(int s) { return X(Y(s)); }
    

    That is, Z is the composition of X and Y . But you cannot do that because X takes an int , and Y returns a Nullable<int> . But since you have the "bind" operation, you can make this work:

    Nullable<int> Z(int s) { return Bind(Y(s), X); }
    

    The bind operation on a monad is what makes composition of functions on amplified types work. The "rules" I handwaved about above are that the monad preserves the rules of normal function composition; that composing with identity functions results in the original function, that composition is associative, and so on.

    In C#, "Bind" is called "SelectMany". Take a look at how it works on the sequence monad. We need to have three things: turn a value into a sequence, turn a sequence into a value, and bind operations on sequences. Those operations are:

    IEnumerable<T> MakeSequence<T>(T item)
    {
        yield return item;
    }
    T Single<T>(IEnumerable<T> sequence)
    {
        // let's just take the first one
        foreach(T item in sequence) return item; 
    }
    IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
    {
        foreach(T item in seq)
            foreach(T result in func(item))
                yield return result;            
    }
    

    The nullable monad rule was "to combine two functions that produce nullables together, check to see if the inner one results in null; if it does, produce null, if it does not, then call the outer one with the result". That's the desired semantics of nullable. The sequence monad rule is "to combine two functions that produce sequences together, apply the outer function to every element produced by the inner function, and then concatenate all the resulting sequences together". The fundamental semantics of the monads are captured in the Bind / SelectMany methods; this is the method that tells you what the monad really means.

    We can do even better. Suppose you have a sequences of ints, and a method that takes ints and results in sequences of strings. We could generalize the binding operation to allow composition of functions that take and return different amplified types, so long as the inputs of one match the outputs of the other:

    IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
    {
        foreach(T item in seq)
            foreach(U result in func(item))
                yield return result;            
    }
    

    So now we can say "amplify this bunch of individual integers into a sequence of integers. Transform this particular integer into a bunch of strings, amplified to a sequence of strings. Now put both operations together: amplify this bunch of integers into the concatenation of all the sequences of strings." Monads allow you to compose your amplifications.

    What problem does it solve and what are the most common places it's used?

    That's rather like asking "what problems does the singleton pattern solve?", but I'll give it a shot.

    Monads are typically used to solve problems like:

  • I need to make new capabilities for this type and still combine old functions on this type to use the new capabilities.
  • I need to capture a bunch of operations on types and represent those operations as composable objects, building up larger and larger compositions until I have just the right series of operations represented, and then I need to start getting results out of the thing
  • I need to represent side-effecting operations cleanly in a language that hates side effects
  • (Note that these are basically three ways of saying the same thing.)

    C# uses monads in its design. As already mentioned, the nullable pattern is highly akin to the "maybe monad". LINQ is entirely built out of monads; the SelectMany method is what does the semantic work of composition of operations. (Erik Meijer is fond of pointing out that every LINQ function could actually be implemented by SelectMany ; everything else is just a convenience.)

    To clarify the kind of understanding I was looking for, let's say you were converting an FP application that had monads into an OOP application. What would you do to port the responsibilities of the monads into the OOP app?

    Most OOP languages do not have a rich enough type system to represent the monad pattern itself directly; you need a type system that supports types that are higher types than generic types. So I wouldn't try to do that. Rather, I would implement generic types that represent each monad, and implement methods that represent the three operations you need: turning a value into an amplified value, turning an amplified value into a value, and transforming a function on unamplified values into a function on amplified values.

    A good place to start is how we implemented LINQ in C#. Study the SelectMany method; it is the key to understanding how the sequence monad works in C#. It is a very simple method, but very powerful!

    For a more in-depth and theoretically sound explanation of monads in C#, I highly recommend my colleague Wes Dyer's article on the subject. This article is what explained monads to me when they finally "clicked" for me.

    The Marvels of Monads


    Illustrations are borrowed from:
    Awesomely descriptive JavaScript with monads presentation by Michal Nowak
    and
    A Hyperturtle Monad Makes Pretty Pictures article by Jonathan Vincent Toups.


    Why do we need monads?

  • We want to program only using functions . ("functional programming" after all -FP).
  • Then, we have a first big problem. This is a program:

    f(x) = 2 * x

    g(x,y) = x / y

    How can we say what is to be executed first ? How can we form an ordered sequence of functions (ie a program ) using no more than functions?

    Solution: compose functions . If you want first g and then f , just write f(g(x,y)) . OK, but ...

  • More problems: some functions might fail (ie g(2,0) , divide by 0). We have no "exceptions" in FP . How do we solve it?

    Solution: Let's allow functions to return two kind of things : instead of having g : Real,Real -> Real (function from two reals into a real), let's allow g : Real,Real -> Real | Nothing g : Real,Real -> Real | Nothing (function from two reals into (real or nothing)).

  • But functions should (to be simpler) return only one thing .

    Solution: let's create a new type of data to be returned, a " boxing type " that encloses maybe a real or be simply nothing. Hence, we can have g : Real,Real -> Maybe Real . OK, but ...

  • What happens now to f(g(x,y)) ? f is not ready to consume a Maybe Real . And, we don't want to change every function we could connect with g to consume a Maybe Real .

    Solution: let's have a special function to "connect"/"compose"/"link" functions . That way, we can, behind the scenes, adapt the output of one function to feed the following one.

    In our case: g >>= f (connect/compose g to f ). We want >>= to get g 's output, inspect it and, in case it is Nothing just don't call f and return Nothing ; or on the contrary, extract the boxed Real and feed f with it. (This algorithm is just the implementation of >>= for the Maybe type).

  • Many other problems arise which can be solved using this same pattern: 1. Use a "box" to codify/store different meanings/values, and have functions like g that return those "boxed values". 2. Have composers/linkers g >>= f to help connecting g 's output to f 's input, so we don't have to change f at all.

  • Remarkable problems that can be solved using this technique are:

  • having a global state that every function in the sequence of functions ("the program") can share: solution StateMonad .

  • We don't like "impure functions": functions that yield different output for same input. Therefore, let's mark those functions, making them to return a tagged/boxed value: IO monad.

  • Total happiness !!!!


    In terms that an OOP programmer would understand (without any functional programming background), what is a monad?

    What problem does it solve and what are the most common places it's used?are the most common places it's used?

    In terms of OO programming, a monad is an interface (or more likely a mixin), parameterized by a type, with two methods, return and bind that describe:

  • How to inject a value to get a monadic value of that injected value type;
  • How to use a function that makes a monadic value from a non-monadic one, on a monadic value.
  • The problem it solves is the same type of problem you'd expect from any interface, namely, "I have a bunch of different classes that do different things, but seem to do those different things in a way that has an underlying similarity. How can I describe that similarity between them, even if the classes themselves aren't really subtypes of anything closer than 'the Object' class itself?"

    More specifically, the Monad "interface" is similar to IEnumerator or IIterator in that it takes a type that itself takes a type. The main "point" of Monad though is being able to connect operations based on the interior type, even to the point of having a new "internal type", while keeping - or even enhancing - the information structure of the main class.

    链接地址: http://www.djcxy.com/p/8360.html

    上一篇: Monad在非

    下一篇: Monad用简单的英语? (对于没有FP背景的OOP程序员)