do-notation
do-notation copied to clipboard
The Haskell’s do notation brought to Rust
do-notation, the monadic do notation brought to Rust.
This crate provides the m! macro, which provides the Haskell monadic syntactic sugar do.
Note: it is not possible to use the
do!syntax asdois a reserved keyword in Rust.
The syntax is very similar to what you find in Haskell:
- You use the
m!macro; in Haskell, you use thedokeyword. - The
<-syntactic sugar binds its left hand side to the monadic right hand side by entering the right side via a closure. - Like almost any statement in Rust, you must end your statement with a semicolon (
;). - The last line must be absent of
;or contains thereturnkeyword. - You can use
returnnowhere but on the last line. - A line containing a single expression with a semicolon is a valid statement and has the same effect as
_ <- expr. letbindings are allowed in the formlet <pattern> = <expr>;and have the regular Rust meaning.- The
donotation syntax does not extend into inner code blocks; however, it can have its ownm!block. For example:m! { outer_do... if exp { m! { inner_do... } } else { ... } ... }.
How do I make my monad works with m!?
Because monads are higher-kinded types, it is not possible to define the monadic do-notation in a fully type-system
elegant way. However, this crate is based on the rebindable concept in Haskell (i.e. you can change what the >>=
operator’s types are), so m! has one type-system requirement and one syntactic requirement.
First, you have to implement one trait: [Lift], which allows to lift a value A into a monadic structure of
A. For instance, lifting a A into the Option monad yields an Option<A>.
Then, you have to provide an and_then method, which is akin to Haskell’s >>= operator. The choice of using
and_then and not a proper name like flat_map or bind is due to the current state of the standard-library —
monads like Option and Result<_, E> don’t have flat_map defined on them but have and_then. The type signature
is not enforced, but:
and_thenmust be a binary function taking a typeA, a closureA -> Monad<B>and returnsMonad<B>, whereMonadis the monad you are addingand_thenfor. For instance, if you are implementing it forOption,and_thentakes anA, a closureA -> Option<B>and returns anOption<B>.and_thenmust move its first argument, which has to beself. The type ofSelfis not enforced.and_then’s closure must takeAwith aFnOnceclosure.
Meaning of the <- operator
The <- syntactic sugar is not strictly speaking an operator: it’s not valid vanilla Rust. Instead, it’s a trick
defined in the m! allowing to use both [Lift::lift] and and_then. When you look at code inside a do-notation
block, every monadic statements (separated with ; in this crate) can be imagined as a new level of nesting inside
a closure — the one passed to and_then, indeed.
First example: fallible code
One of the first monadic application that people learn is the fallible effect — Maybe in Haskell.
In Rust, it’s Option. Option is an interesting monad as it allows you to fail early.
use do_notation::m;
let r = m! {
x <- Some("Hello, world!");
y <- Some(3);
Some(x.len() * y)
};
assert_eq!(r, Some(39));
The binding <- expr syntax unwraps the right part and binds it to binding, making it available to
next calls — remember, nested closures. The final line re-enters the structure (here, Option) explicitly.
Note that it is possible to re-enter the structure without having to specify how / knowing the structure
(with Option, you re-enter with Some). You can use the return keyword, that will automatically lift the
value into the right structure:
use do_notation::m;
let r = m! {
x <- Some(1);
y <- Some(2);
z <- Some(3);
return [x, y, z];
};
assert_eq!(r, Some([1, 2, 3]));