Crates.io | rsmonad |
lib.rs | rsmonad |
version | 0.2.4 |
source | src |
created_at | 2023-05-18 08:38:24.011168 |
updated_at | 2023-05-25 01:55:08.867884 |
description | Haskell-style monads for idiomatic Rust. |
homepage | |
repository | https://github.com/wrsturgeon/rsmonad |
max_upload_size | |
id | 867617 |
size | 88,993 |
Haskell-style monads with macro-free Rust syntax.
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 do
-notation:
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 join
-in-terms-of-bind
with no type annotations necessary:
let li = list![list![0_u8]]; // List<List<u8>>
let joined = li.join(); // --> List<u8>!
assert_eq!(joined, list![0]);
>>=
to be self-modifying, so we use >>
instead. return
(keyword) becomes consume
and Haskell's >>
becomes &
.>>=
instead of <$>
: it's x.fmap(f)
to mean f <$> x
.
fmap(f, x)
or x.fmap(f)
, or you can pipe it: x % f % g % ...
.Applicative
uses *
instead of <*>
(or the explicit method tie
).Alternative
uses |
instead of <|>
(or the explicit method either
).+
instead of <>
.Just write a monad! { ...
and you get all its superclasses (Functor
, Applicative
, Alternative
, ...) for free, plus property-based tests of the monad laws:
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);
Catch panic
s without worrying about the details:
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,
);
Right now, you can use >>
for bind
only when you have a concrete instance of Monad
like Maybe
but not a general <M: Monad<A>>
.
The latter still works but requires an explicit call to 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.
...
in this scope" in a doctestDoctests try to guess where to place a fn main { ... }
if you don't provide one, and sometimes it reads an rsmonad
macro as something that should be in a main
block.
Try adding an explicit fn 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:
/// monad! { ... }
/// # fn main {
/// a();
/// b();
/// c();
/// # }
#![no_std]
Disable default features:
# Cargo.toml
[dependencies]
rsmonad = { version = "*", default-features = false }
Note that this will also disable List
,though this is probably what you want: we can't know its length at compile time (that's the point of its bind
implementation), so it requires a heap.
An alloc
feature is in the works for #![no_std] extern crate alloc;
crates, but it's not finalized yet.