Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hadronized/do-notation
The Haskell’s do notation brought to Rust
https://github.com/hadronized/do-notation
Last synced: about 1 month ago
JSON representation
The Haskell’s do notation brought to Rust
- Host: GitHub
- URL: https://github.com/hadronized/do-notation
- Owner: hadronized
- Created: 2020-04-10T18:09:30.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-01-03T22:05:48.000Z (almost 3 years ago)
- Last Synced: 2024-10-30T18:42:51.613Z (about 2 months ago)
- Language: Rust
- Size: 22.5 KB
- Stars: 55
- Watchers: 5
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# `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 as `do` is 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 the `do` keyword.
- 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 the `return` keyword.
- You can use `return` nowhere 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`.
- `let` bindings are allowed in the form `let = ;` and have the regular Rust meaning.
- The `do` notation syntax does not extend into inner code blocks; however, it can have its own `m!` 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`.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` and returns `Monad`, 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` and returns an `Option`.
- `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.## 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.```rust
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:```rust
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]));
```