4 ms·
One reason is that they allow you to write general, primitive functions, and then compose them together or hoist them up into the same category. Consider this
by gamegoblin 11y ago
One reason is that they allow you to write general, primitive functions, and then compose them together or hoist them up into the same category.
Consider this example, written in Java 8.
// Maybe<T> can be Nothing(), representing failure, or Just(X) representing success.
public abstract class Maybe<T> {}
public class Just<T> extends Maybe<T> {
public final T value;
public Just(T value) { this.value = value; }
}
public class Nothing<T> extends Maybe<T> {}
Now consider these function signatures:
// Returns Nothing() if argument is negative
public Maybe<Double> squareRoot(double x);
// Returns Nothing() if divisor is 0
public Maybe<Double> divide(double x, double y);
What do I do if I want to chain these functions together to write a "square root and divide" function?
// Returns Nothing() if dividend is negative or divisor is 0
public Maybe<Double> squareRootAndDivide(double x, double y) {
Maybe<Double> sqrt = squareRoot(x);
if(sqrt instanceof Just<Double>) {
return divide(sqrt.value, y);
} else {
return new Nothing();
}
}
This is kind of ugly. And if I want to compose lots of Maybe values, all of this instanceof nonsense (or having a tagged union in C or whatever) becomes extremely tedious.
Better is to define a function to contain this logic:
// Returns Nothing() if x is Nothing(), otherwise returns f(unwrapped x)
public <A, B> Maybe<B> bind(Maybe<A> x, Function<A, Maybe<B>> f) {
if(x instanceof Just<A>) {
return f.apply(x.value);
} else {
return new Nothing();
}
}
Now I can write the above squareRootAndDivide code like:
public Maybe<Double> squareRootAndDivide(double x, double y) {
return bind(squareRoot(x), ::divide);
}
Now I can chain any number of calls that may all end in failure, not having to worry where the failure takes place:
return bind(bind(foo(x), ::bar), ::baz);
That looks kind of stupid. We could define `bind` to be a member of the abstract class Maybe, and refer to `this` instead of `x`, then we could rewrite that like:
return x.bind(::foo).bind(::bar).bind(::baz);
Which in Haskell would look like
foo x >>= bar >>= baz
Quite clean in both languages!
Of course, Java might be a bad example, because you might say "Well, I would just throw an exception for any of those illegal arguments, and that would also short-circuit the computation". This is true.
In Haskell, there is the Either type, which is similar to the Maybe type except instead of Nothing and Just you have Left and Right. The difference in Left and Nothing is Left can contain some information (such as a string explaining what failure happened, like a Java exception). Like Maybe, Either is also a monad, and can be chained together in much the same way, except instead of short circuiting on Nothing, it short circuits on Left.
So in Java, it's almost as if you are always living in the Either monad without even knowing it! Of course, this is a gross abuse of terminology.
There is also far more that can be done with monads than representing failure (although they are great for that in the above examples, or in parsers, etc). This is just a simple example.
It also might be interested to note that you cannot construct a Monad interface in Java that has this `bind` function, because Java lacks Higher-Kinded Types. Consider a type in java Foo<A, B, C>. It doesn't make sense to talk about Foo<A, B>, because Foo takes 3 type parameters. In Haskell we can talk about Foo<A, B> as a "higher kinded" type that takes another type as an argument (C) to produce a concrete type of Foo<A, B, C>.
As such, we have to code an ad-hoc monadic interface for every type we want to have one, but this means we can't write functions that operate on any generic monadic type.
Rust is currently running into this -- they have monad-like interfaces for Result and Option (Rust versions of Either and Maybe) where the function and_then is their bind. I would like to write a function which can say, go from Vec<Option<u32>> to Option<Vec<u32>. Where it returns None if any of the elements of the Vec were None.
I would also like to write a function that goes from Vec<Result<Err, u32>> to Result<Err, Vec<u32>>, giving me Err if any of the elements of the Vec were Err. You can see how this is the same pattern as above. In Haskell, with Higher-Kinded types, we can write this function generlized to all monads (called `sequence` in the standard library), but in Rust or Java, I would have to write this function for each particular instance.
You might find it valuable to read something like [1], but it might use too much Haskell syntax to be particularly readable. Give it a shot, anyway. Specifically for Monads, the section about the Kleisli category is the most applicable.
[1] http://www.haskellforall.com/2012/08/the-category-design-pattern.html http://www.haskellforall.com/2012/08/the-category-design-pat...