Crates.io | do-notation |
lib.rs | do-notation |
version | 0.1.3 |
source | src |
created_at | 2021-01-06 17:41:48.553271 |
updated_at | 2021-01-08 00:30:15.427623 |
description | Monadic do syntactic sugar |
homepage | https://github.com/phaazon/do-notation |
repository | https://github.com/phaazon/do-notation |
max_upload_size | |
id | 333257 |
size | 14,897 |
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 asdo
is a reserved keyword in Rust.
The syntax is very similar to what you find in Haskell:
m!
macro; in Haskell, you use the do
keyword.<-
syntactic sugar binds its left hand side to the monadic right hand side
by entering the right side via a closure.;
).;
or contains the return
keyword.return
nowhere but on the last line._ <- expr
.let
bindings are allowed in the form let <pattern> = <expr>;
and have the regular Rust meaning.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_then
must be a binary function taking a type A
, a closure A -> Monad<B>
and returns Monad<B>
, where
Monad
is the monad you are adding and_then
for. For instance, if you are implementing it for Option
,
and_then
takes an A
, a closure A -> Option<B>
and returns an Option<B>
.and_then
must move its first argument, which has to be self
. The type of Self
is not enforced.and_then
’s closure must take A
with a FnOnce
closure.<-
operatorThe <-
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.
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]));