do_notation/lib.rs
1//! # `do-notation`, the monadic `do` notation brought to Rust.
2//!
3//! This crate provides the `m!` macro, which provides the Haskell monadic syntactic sugar `do`.
4//!
5//! > Note: it is not possible to use the `do!` syntax as `do` is a reserved keyword in Rust.
6//!
7//! The syntax is very similar to what you find in Haskell:
8//!
9//! - You use the `m!` macro; in Haskell, you use the `do` keyword.
10//! - The `<-` syntactic sugar binds its left hand side to the monadic right hand side
11//! by _entering_ the right side via a closure.
12//! - Like almost any statement in Rust, you must end your statement with a semicolon (`;`).
13//! - The last line must be absent of `;` or contains the `return` keyword.
14//! - You can use `return` nowhere but on the last line.
15//! - A line containing a single expression with a semicolon is a valid statement and has the same effect as `_ <- expr`.
16//! - `let` bindings are allowed in the form `let <pattern> = <expr>;` and have the regular Rust meaning.
17//!
18//! ## How do I make my monad works with `m!`?
19//!
20//! Because monads are higher-kinded types, it is not possible to define the monadic do-notation in a fully type-system
21//! elegant way. However, this crate is based on the rebindable concept in Haskell (i.e. you can change what the `>>=`
22//! operator’s types are), so `m!` has one type-system requirement and one syntactic requirement.
23//!
24//! First, you have to implement one trait: [`Lift`], which allows to _lift_ a value `A` into a _monadic structure of
25//! `A`_. For instance, lifting a `A` into the `Option` monad yields an `Option<A>`.
26//!
27//! Then, you have to provide an `and_then` method, which is akin to Haskell’s `>>=` operator. The choice of using
28//! `and_then` and not a proper name like `flat_map` or `bind` is due to the current state of the standard-library —
29//! monads like `Option` and `Result<_, E>` don’t have `flat_map` defined on them but have `and_then`. The type signature
30//! is not enforced, but:
31//!
32//! - `and_then` must be a binary function taking a type `A`, a closure `A -> Monad<B>` and returns `Monad<B>`, where
33//! `Monad` is the monad you are adding `and_then` for. For instance, if you are implementing it for `Option`,
34//! `and_then` takes an `A`, a closure `A -> Option<B>` and returns an `Option<B>`.
35//! - `and_then` must move its first argument, which has to be `self`. The type of `Self` is not enforced.
36//! - `and_then`’s closure must take `A` with a `FnOnce` closure.
37//!
38//! ## Meaning of the `<-` operator
39//!
40//! The `<-` syntactic sugar is not strictly speaking an operator: it’s not valid vanilla Rust. Instead, it’s a trick
41//! defined in the `m!` allowing to use both [`Lift::lift`] and `and_then`. When you look at code inside a do-notation
42//! block, every monadic statements (separated with `;` in this crate) can be imagined as a new level of nesting inside
43//! a closure — the one passed to `and_then`, indeed.
44//!
45//! ## First example: fallible code
46//!
47//! One of the first monadic application that people learn is the _fallible_ effect — `Maybe` in Haskell.
48//! In `Rust`, it’s `Option`. `Option` is an interesting monad as it allows you to fail early.
49//!
50//! ```rust
51//! use do_notation::m;
52//!
53//! let r = m! {
54//! x <- Some("Hello, world!");
55//! y <- Some(3);
56//! Some(x.len() * y)
57//! };
58//!
59//! assert_eq!(r, Some(39));
60//! ```
61//!
62//! The `binding <- expr` syntax unwraps the right part and binds it to `binding`, making it available to
63//! next calls — remember, nested closures. The final line re-enters the structure (here, `Option`) explicitly.
64//!
65//! Note that it is possible to re-enter the structure without having to specify how / knowing the structure
66//! (with `Option`, you re-enter with `Some`). You can use the `return` keyword, that will automatically lift the
67//! value into the right structure:
68//!
69//! ```rust
70//! use do_notation::m;
71//!
72//! let r = m! {
73//! x <- Some(1);
74//! y <- Some(2);
75//! z <- Some(3);
76//! return [x, y, z];
77//! };
78//!
79//! assert_eq!(r, Some([1, 2, 3]));
80//! ```
81
82#[macro_export]
83macro_rules! m {
84 // return
85 (return $r:expr ;) => {
86 $crate::Lift::lift($r)
87 };
88
89 // let-binding
90 (let $p:pat = $e:expr ; $($r:tt)*) => {{
91 let $p = $e;
92 m!($($r)*)
93 }};
94
95 // const-bind
96 (_ <- $x:expr ; $($r:tt)*) => {
97 $x.and_then(move |_| { m!($($r)*) })
98 };
99
100 // bind
101 ($binding:ident <- $x:expr ; $($r:tt)*) => {
102 $x.and_then(move |$binding| { m!($($r)*) })
103 };
104
105 // const-bind
106 ($e:expr ; $($a:tt)*) => {
107 $e.and_then(move |_| m!($($a)*))
108 };
109
110 // pure
111 ($a:expr) => {
112 $a
113 }
114}
115
116/// Lift a value inside a monad.
117pub trait Lift<A> {
118 /// Lift a value into a default structure.
119 fn lift(a: A) -> Self;
120}
121
122impl<A> Lift<A> for Option<A> {
123 fn lift(a: A) -> Self {
124 Some(a)
125 }
126}
127
128impl<A, E> Lift<A> for Result<A, E> {
129 fn lift(a: A) -> Self {
130 Ok(a)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn option() {
140 let r: Option<i32> = m! {
141 v <- Some(3);
142 Some(v)
143 };
144
145 assert_eq!(r, Some(3));
146
147 let r: Option<i32> = m! {
148 v <- r;
149 x <- Some(10);
150 Some(v * x)
151 };
152
153 assert_eq!(r, Some(30));
154
155 let n: Option<i32> = None;
156 let r: Option<i32> = m! {
157 v <- Some(314);
158 x <- n;
159 Some(v * x)
160 };
161
162 assert_eq!(r, None);
163
164 let r = m! {
165 _ <- Some("a");
166 b <- Some("b");
167 _ <- Option::<&str>::None;
168 Some(b)
169 };
170
171 assert_eq!(r, None);
172
173 let r = m! {
174 _ <- Some("a");
175 return "b";
176 };
177
178 assert_eq!(r, Some("b"));
179 }
180
181 #[test]
182 fn result() {
183 let r: Result<i32, &str> = m! {
184 v <- Ok(3);
185 Ok(v)
186 };
187
188 assert_eq!(r, Ok(3));
189
190 let r: Result<i32, &str> = m! {
191 v <- r;
192 x <- Ok(10);
193 Ok(v * x)
194 };
195
196 assert_eq!(r, Ok(30));
197
198 let n: Result<i32, &str> = Err("error");
199 let r: Result<i32, &str> = m! {
200 v <- Ok(314);
201 x <- n;
202 Ok(v * x)
203 };
204
205 assert_eq!(r, Err("error"));
206
207 let r = m! {
208 _ <- Result::<&str, &str>::Ok("a");
209 b <- Ok("b");
210 _ <- Result::<&str, &str>::Err("nope");
211 Ok(b)
212 };
213
214 assert_eq!(r, Err("nope"));
215
216 fn guard<E>(cond: bool, err: E) -> Result<(), E> {
217 if cond {
218 Ok(())
219 } else {
220 Err(err)
221 }
222 }
223
224 let r = m! {
225 x <- Ok(true);
226 _ <- guard(1 == 2, "meh");
227 Ok(x)
228 };
229
230 assert_eq!(r, Err("meh"));
231 }
232
233 #[test]
234 fn instruction_counter() {
235 struct IC<A> {
236 count: usize,
237 value: A,
238 }
239
240 impl<A> IC<A> {
241 fn new(value: A) -> Self {
242 IC { count: 1, value }
243 }
244
245 fn value(&self) -> &A {
246 &self.value
247 }
248
249 fn count(&self) -> usize {
250 self.count
251 }
252
253 fn and_then<B>(self, f: impl FnOnce(A) -> IC<B>) -> IC<B> {
254 let r = f(self.value);
255
256 IC {
257 count: self.count + r.count,
258 value: r.value,
259 }
260 }
261 }
262
263 impl<A> Lift<A> for IC<A> {
264 fn lift(value: A) -> Self {
265 Self::new(value)
266 }
267 }
268
269 let ic = m! {
270 a <- IC::new(10);
271 b <- IC::new(2);
272 IC::new(a + b)
273 };
274
275 assert_eq!(ic.value(), &12);
276 assert_eq!(ic.count(), 3);
277
278 let ic = m! {
279 _ <- IC::new("a");
280 let x = 2;
281
282 // test statements
283 let y = if 1 == 1 { 3 } else { 0 };
284
285 _ <- IC::new("b");
286 _ <- IC::new("c");
287
288 return [1, x, y];
289 };
290
291 assert_eq!(ic.value(), &[1, 2, 3]);
292 assert_eq!(ic.count(), 4);
293 }
294}