'Useful monads for multi-paradigm languages
I finally got my head around monads once I understood how they are useful in c++/python by allowing you to chain together functions of the form T -> Generic<U>
. For example, if you have
readfile = [](string s) -> optional<string> {...};
http_request = [](string s) -> optional<int> {...};
inverse = [](int i) -> optional<int> {...}
then bind >>= :: optional<T> -> (T -> optional<U>) -> optional<U>
has exactly the right signature to allow you to compose these functions
optional{"myfile"} >>= readfile >>= http_request >>= inverse;
// or in haskell
read_request_inverse = (>>= inverse) . (>>= http_request) . readfile
instead of writing out the short circuiting conditionals by hand
[]() -> optional<int>
{
auto message = readfile("myfile");
if (!message)
return nullopt
auto number = http_request(*message)
if (!number)
return nullopt
return inverse(*number)
}
I think what tripped me up was not distinguishing chained binds from nested binds (which Bartosz Milewski demonstrates with c++ ranges) and understanding them separately
auto triples =
for_each(ints(1), [](int z) {
return for_each(ints(1, z), [=](int x) {
return for_each(ints(x, z), [=](int y) {
return yield_if(x*x + y*y == z*z, std::make_tuple(x, y, z));
});
});
});
which is just list comprehension
triples = [(x, y, z) | z <- [1..]
, x <- [1..z]
, y <- [x..z]
, x^2 + y^2 == z^2]
Also I believe I got tripped up on the fact that the reader, writer, and state monads are just trying to reverse engineer side-effects (which I'm not entirely sure doesn't reintroduce the problems of impure procedures/subroutines) and so aren't useful in multi-paradigm languages where you can just have real (sensibly constrained) side-effects.
So monadic optional<T>
/result<T,E>
seems highly useful in c++/python/rust, and monadic ranges/lists might be useful solving coding challenges but not really for real life problems.
So my question is are there any other examples of monads that can justify the usefulness of monads in multi-paradigm languages?
Solution 1:[1]
Programming languages that make the most use of monads are, of course, functional programming languages. You are using Haskell in your question, but monads are also useful in OCaml.
A first example of this programming pattern being useful is in Nix. Nix is a package manager, that uses scripts written in nix (yes, there's a bit of overlap with terminology) to describe packaging, as well as other things. If you were wondering, yes, nix is a pure, lazy functional programming language. It goes as far as having an entire OS whose configuration is written in nix, called NixOS. The monad patterns appears quite often when you package applications, although in a quite convoluted way. The idea is that packages themselves are monads, having a functor that allows you to chain modifications of the package. In pseudo-code, keeping your notation, it could be something like package >>= apply_patch >>= change_version >>= add_tests
.
An other example is in Rust. Contrary to Haskell, OCaml or nix, Rust is not a functional progrmaming language (more of a system programming language), but it has many aspects of a functional programming language, and many of their patterns, including the option
one, for instance, which is, in Rust, called Option<T>
(being generic over T
). Rust provides several methods besides the basic map
functor to deal with Option<T>
objects, so typical Rust code might look like (assuming a: Option<T>
)
a
.map(|x| ...)
.filter(|x| ...)
.and_then(|x| ...)
.or_default()
However, Rust does not entierly deals with monads in the same way Haskell, say, would deal with them, because in Rust it's quite common that a function that is monad-compatible (ie. does_something: fn(U) -> Option<T>
) will actually unpack the monad-contained values, operate directly on that value, and then rewrap them. This is made simple with the ?
operator, which is just syntax sugar for a macro (the try!
one). The usage is the following, if a: Option<T>
.
fn do_something_with_a(a: Option<T>) -> Option<U> {
let b = do_something_with_a_value(a?);
Some(b)
}
Here, the ?
operators acts by first match
ing over a
. If it's a None
, then the function immediately returns None
. Otherwise, it's a Some(x)
, and x
is returned. That is, this ?
operator acts very much like >>=
, except that it does not take a closure, and run it on x
if a
is Some(x)
. Instead, it will make as if the current function was that closure, by letting it operating directly on x
.
So far, so good. But Rust has other predefined types that have this monadic pattern, which are not in your original question. For instance, Result<T, E>
. In Rust, you usually want to avoid throw
, to panic!
, to raise
, or however you want to call it. This is not how exception management is done. Instead, if a function that returns a type T
could throw an error, of type E
, then it should instead return Result<T, E>
. It is (as you might imagine) simply defined as an enum, just like Option<T>
was:
enum Result<T, E> {
Ok(T),
Err(E),
}
and, just like Option<T>
, Rust provides a basic functor to act this type, which is map
(ie. my_result.map(|x| ...)
). However, just like Option
, it also provides plenty of other methods to work with Result
objects, but there is one way that is particularly idiomatic: the ?
operator (yes, again)! Just like with an option, what is does is the following:
- if the object is of the form
Ok(x)
, then its value isx
; - otherwise, it's of the form
Err(y)
, and it will return from the current function withErr(y)
.
Actually, ?
is even a bit smarter than that because your function might call several functions that return Result<_, _>
, but with different error-types. For instance, if you try to read from a file, you may get an IO error. Then you try to parse some data, and you may get a parser error. Then you do some arithmetic and you may get an arithmetic error. To deal with that, your function should return a generic-enough error so that ?
may perform conversion between the errors that you may get (IO, parse, arithmetic, ...) and the one that you return.
Solution 2:[2]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | BlackBeans |
Solution 2 |