### 7 releases

0.2.4 | May 25, 2023 |
---|---|

0.2.2 | May 23, 2023 |

0.1.2 | May 19, 2023 |

#**105** in Math

**247** downloads per month

**MPL-2.0**and LGPL-3.0-or-later

73KB

1.5K
SLoC

# Monads, Functors, & More in Stable Rust

Haskell-style monads with macro-free Rust syntax.

## Types work with zero annotations

A fancy example (that compiles and runs as a test in

):`gallery .rs`

`assert_eq!``(`
`vec!``[`
`||` `"`this string isn't a number`"``,`
`|``|` `"`67`"``,`
`|``|` `"`that was, but not binary`"``,`
`|``|` `"`1010101010`"``,`
`|``|` `"`that was, but more than 8 bits`"``,`
`|``|` `"`101010`"``,`
`|``|` `"`that one should work!`"``,`
`|``|` `panic!``(``"`lazy evaluation!`"``)``,`
`]`
`.``fmap``(``|``x``|` `move` `||` `u8``::`from_str_radix`(``x``(``)``,` `2``)``.``ok``(``)``)`
`.``asum``(``)``,`
`Some``(``42``)`
`)``;`

Desugared

-notation:`do`

`assert_eq!``(`
`list!``[``1``..``20``]`
`>``>` `|`x`|` `{`
`list!``[``{` x `}``..``20``]`
`>``>` `|`y`|` `{`
`list!``[``{` y `}``..``20``]`
`>``>` `|`z`|` `guard``::``<`List`<``_``>``>``(`x `*` x `+` y `*` y `==` z `*` z`)` `>``>` `|``_``|` `list!``[``(`x`,` y`,` z`)``]`
`}`
`}``,`
`list!``[`
`//` Including "non-primitive" (i.e. multiples of smaller) triples
`(``3``,` `4``,` `5``)``,`
`(``5``,` `12``,` `13``)``,`
`(``6``,` `8``,` `10``)``,`
`(``8``,` `15``,` `17``)``,`
`(``9``,` `12``,` `15``)`
`]`
`)``;`

The logic of Haskell lists with the speed of Rust vectors:

`use` `rsmonad``::``prelude``::``*``;`
`let` li `=` `list!``[``1``,` `2``,` `3``,` `4``,` `5``]``;`
`fn` `and_ten``(``x``:` `u8``)`` ``->` `List``<``u8``>` `{` `list!``[`x`,` `10` `*` x`]` `}`
`assert_eq!``(`li `>``>` and_ten`,` `list!``[``1``,` `10``,` `2``,` `20``,` `3``,` `30``,` `4``,` `40``,` `5``,` `50``]``)``;`

*N*-fold bind without type annotations:

`//` from the wonderful Haskell docs: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/List
`fn` `bunny``(``s``:` `&``str``)`` ``->` `List``<``&``str``>` `{`
`list!``[`s`,` s`,` s`]`
`}`
`assert_eq!``(`
`list!``[``"`bunny`"``]` `>``>` bunny`,`
`list!``[``"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``]``,`
`)``;`
`assert_eq!``(`
`list!``[``"`bunny`"``]` `>``>` bunny `>``>` bunny`,`
`list!``[``"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``,` `"`bunny`"``]``,`
`)``;`

And even the notoriously tricky

-in-terms-of-`join`

with no type annotations necessary:`bind`

`let` li `=` `list!``[``list!``[``0_``u8``]``]``;` `//` List<List<u8>>
`let` joined `=` li`.``join``(``)``;` `//` --> List<u8>!
`assert_eq!``(`joined`,` `list!``[``0``]``)``;`

## Syntactic sugar

- Rust requires

to be self-modifying, so we use`>>=`

instead.`>``>`

(keyword) becomes`return`

and Haskell's`consume`

becomes`>``>`

.`&` - Function-application order is standardized across the board to match

instead of`>>=`

: it's`<``$``>`

to mean`x``.``fmap``(`f`)`

.`f``<``$``>`x- I think this makes it easier to read as a chain of computations, but I'm not dead-set on it, and the type system would work either way.

- For functors, you can use

or`fmap``(`f`,`x`)`

, or you can`x``.``fmap``(`f`)`*pipe*it:

.`x``%`f`%`g`%``...`

uses`Applicative`

instead of`*`

(or the explicit method`<``*``>`

).`tie`

uses`Alternative`

instead of`|`

(or the explicit method`<``|``>`

).`either`- Monoids use

instead of`+`

.`<``>`

## Use

Just write a

and you get all its superclasses (`monad!` `{` `...`

, `Functor`

, `Applicative`

, ...) for free, plus property-based tests of the monad laws:`Alternative`

`use` `rsmonad``::``prelude``::``*``;`
`enum` `Maybe`<A> `{`
Just`(`A`)``,`
Nothing`,`
`}`
`monad!` `{`
`Maybe``<`A`>``:`
`fn` `consume``(`a`)`` ``{`
Just`(`a`)`
`}`
`fn` `bind``(``self`, f`)`` ``{`
`match` `self` `{`
Just`(`a`)` `=>` `f``(`a`)``,`
Nothing `=>` Nothing`,`
`}`
`}`
`}`
`//` And these just work:
`//` Monad
`assert_eq``(`Just`(``4``)` `>``>` `|`x`|` `u8``::`checked_add`(`x`,` `1``)``.``into``(``)``,` Just`(``5``)``)``;`
`assert_eq``(`Nothing `>``>` `|`x`|` `u8``::`checked_add`(`x`,` `1``)``.``into``(``)``,` Nothing`)``;`
`assert_eq``(`Just`(``255``)` `>``>` `|`x`|` `u8``::`checked_add`(`x`,` `1``)``.``into``(``)``,` Nothing`)``;`
`//` Functor
`assert_eq!``(`Just`(``4``)` `|` `u8``::`is_power_of_two`,` Just`(``true``)``)``;`
`assert_eq!``(`Nothing `|` `u8``::`is_power_of_two`,` Nothing`)``;`

## Rust-specific monads

Catch

s without worrying about the details:`panic`

`fn` `afraid_of_circles``(``x``:` `u8``)`` ``->` `BlastDoor``<``(``)``>` `{`
`if` x `==` `0` `{` `panic!``(``"`aaaaaa!`"``)``;` `}`
Phew`(``(``)``)`
`}`
`assert_eq!``(`
Phew`(``42``)` `>``>` afraid_of_circles`,`
Phew`(``(``)``)`
`)``;`
`assert_eq!``(`
Phew`(``0``)` `>``>` afraid_of_circles`,`
Kaboom`,`
`)``;`

## Sharp edges

Right now, you can use

for `>``>`

only when you have a `bind`*concrete instance* of

like `Monad`

but not a general `Maybe`

.
The latter still works but requires an explicit call to `<`M`:` `Monad <A>`

`>`

`m``.``bind``(`f`)`

(or, if you don't `use`

the trait, `Monad``::``<`A`>``::`bind`(`m`,` f`)`

).
This should be fixed with the Rust's non-lifetime binder feature when it rolls out.### "Cannot find type `...`

in this scope" in a doctest

`...`Doctests try to guess where to place a

if you don't provide one, and sometimes it reads an `fn` `main``{` `...` `}`

macro as something that should be in a `rsmonad`

block.
Try adding an explicit `main`

around the statements you want to run.
If you don't want `fn` `main``(``)`` ``{` `...` `}`

to show up in documentation but can't fix this error, comment it out:`fn` `main``(``)`` ``{` `...` `}`

`///` monad! { ... }
`///` # fn main {
`///` a();
`///` b();
`///` c();
`///` # }

`#!``[``no_std``]`

`#!``[``no_std``]`Disable default features:

`# Cargo.toml`
`[``dependencies``]`
`rsmonad = { version = "*", default-features ``=` `false` }

Note that this will also disable

,though this is probably what you want: we `List`*can't* know its length at compile time (that's the point of its

implementation), so it requires a heap.
An `bind`

feature is in the works for `alloc`

crates, but it's not finalized yet.`#!``[``no_std``]``extern` `crate` alloc`;`

#### Dependencies

~1–1.5MB

~34K SLoC