3 ms·
Thanks for taking the time to write all that up. I think (too early to say) that seeing all this expressed in C#/Java(script)/Ruby/Python syntax is key for some
by PopsiclePete 11y ago
Thanks for taking the time to write all that up. I think (too early to say) that seeing all this expressed in C#/Java(script)/Ruby/Python syntax is key for someone like me.
I learned LINQ/Select(Many) and later all the map/filter/reduce functional goodness by playing with Clojure and never had a problem and never heard the word "monad" and was fine, totally fine.
Later watching a video on Rx (MS's reactive extensions) and hearing Erik Meijer talk about monads and Haskell and how Rx was inspired by that is what led me to try to learn Haskell. I just didn't see the "connection" between the functional and reactive patterns I was applying daily without any kind of mental problem and the Haskell stuff which was supposedly "the same", yet so....abstract?
I need to read your sample code and the links more carefully, but maybe this is the "key" I was lacking. Thanks !!!!
- louthy 11y agoMy 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.