Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

My pleasure. I guess to help a bit more (because the list monad tends to be easier to comprehend) is to see how other types are implemented. So here's a very basic implementation of the Option/Maybe monad:

    public class Option<T>
    {
        public readonly bool HasValue;
        public readonly T Value;

        internal Option(bool hasValue, T value)
        {
            HasValue = hasValue;
            Value = value;
        }

        public Option<U> Select<U>(Func<T, U> map) =>
            HasValue
                ? Option.Some<U>(map(Value))
                : Option.None<U>();

        public Option<V> SelectMany<U,V>(Func<T, Option<U>> bind, Func<T,U,V> project) =>
            HasValue
                ? bind(Value).Select(u => project(Value,u))
                : Option.None<V>();
    }

    public static class Option
    {
        public static Option<T> Some<T>(T value) =>
            new Option<T>(true, value);

        public static Option<T> None<T>() =>
            new Option<T>(false, default(T));
    }
The SelectMany implementation is slightly more complicated than I showed before. This is an optimisation that C# does to group the bind and map together. So it may look slightly scary as a function. But hopefully you can see that if the Option<T> has a value then it first invokes bind, then uses the result of the bind (an Option<U>) to project the final result. Here's a more imperative version of it:

        public Option<V> SelectMany<U, V>(Func<T, Option<U>> bind, Func<T, U, V> project)
        {
            if (HasValue)
            {
                var u = bind(Value);

                if (u.HasValue)
                {
                    return Option.Some(project(Value, u.Value));
                }
                else
                {
                    return Option.None<V>();
                }
            }
            else
            {
                return Option.None<V>();
            }
        }
You can see that with the IEnumerable<T> version of Select and SelectMany it encapsulates list iteration. With the Option monad it doesn't do that. It instead checks the HasValue field, and if it's false then it doesn't run the map or bind functions.

The second static class: Option, contains the 'return' functions: Some or None. These wrap a value of type T in an Option<T>.

Now if we use Option<T> in a LINQ expression:

    var option1 = Option.Some(10);
    var option2 = Option.Some(10);
    var none    = Option.None<int>();

    var res1 = from x in option1
               from y in option2
               select x + y;

    // res1.HasValue == true  res1.Value == 20 

    var res2 = from x in option1
               from y in none
               select x + y;

    // res2.HasValue == false

    var res3 = from x in none
               from y in option2
               select x + y;

    // res3.HasValue == false
This is the same as using do notation in Haskell:

    do x <- option1
       y <- option2
       return (x + y)
If we were to do that imperatively it would look like this:

    var res = Option.None<int>();
    if( option1.HasValue )
    {
        if( option2.HasValue )
        {
            res = Option.Some(option1.Value + option2.Value);
        }
    }
Clearly more cluttered and error prone and importantly, not composable. This is where the notion of 'programmable semi-colons' comes from. It appears that the monad is running behaviour 'between the lines', and it is.

Hopefully that clears the fog. I'll keep an eye on this thread for a few days, so feel free to drop any questions in here or on my project page.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: