7个版本
0.2.4 | 2023年5月25日 |
---|---|
0.2.2 | 2023年5月23日 |
0.1.2 | 2023年5月19日 |
#241 in 数学
74KB
1.5K SLoC
稳定Rust中的Monads、Functors及更多
无宏的Rust语法中的Haskell风格monads。
类型无需零注解
一个示例(编译并作为测试运行在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)
);
简化后的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)
]
);
具有Rust向量速度的Haskell列表逻辑
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
// from the wonderful Haskell docs: https://wikibooks.cn/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"],
);
甚至不需要类型注解的著名棘手的join
-in-terms-of-bind
let li = list![list![0_u8]]; // List<List<u8>>
let joined = li.join(); // --> List<u8>!
assert_eq!(joined, list![0]);
语法糖
- Rust要求
>>=
是自修改的,所以我们使用>>
代替。return
(关键字)变为consume
,而Haskell的>>
变为&
。 - 函数应用顺序已标准化,以匹配
>>=
而不是<$>
:它是x.fmap(f)
表示f <$> x
。- 我认为这使它更容易作为一个计算链阅读,但我不坚持这一点,类型系统两种方式都适用。
- 对于functors,你可以使用
fmap(f, x)
或x.fmap(f)
,或者你可以管道化它:x % f % g % ...
。 Applicative
使用*
而不是<*>
(或显式的tie
方法)。Alternative
使用|
而不是<|>
(或显式的either
方法)。- Monoids 使用
+
而不是<>
。
使用
只需写下 monad! { ...
,你就可以免费获得其所有超类(Functor
、Applicative
、Alternative
、...),以及基于属性的 monad 定律测试。
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 特定的 monads
无需担心细节即可捕获 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,
);
锋利边缘
目前,当你有一个类似于 Maybe
的 Monad 的具体实例时,你只能使用
>>
来进行 bind
操作。对于一般形式的 <M: Monad<A>>
,后者仍然可以工作,但需要显式调用 m.bind(f)
(或者如果你没有使用 trait,Monad::<A>::bind(m, f)
)。这应该在 Rust 的非生命周期绑定功能推出时得到解决。
在 doctest 中出现“无法在此作用域中找到类型 ...
”
Doctests 试图猜测 fn main { ... }
的位置,如果你没有提供,有时它将 rsmonad
宏读作应该放在 main
块中的内容。尝试在你想运行的语句周围添加一个显式的 fn main() { ... }
。如果你不想 fn main() { ... }
显示在文档中但无法修复此错误,请将其注释掉。
/// monad! { ... }
/// # fn main {
/// a();
/// b();
/// c();
/// # }
#![no_std]
禁用默认功能
# Cargo.toml
[dependencies]
rsmonad = { version = "*", default-features = false }
请注意,这也会禁用 List
,尽管这可能是你想要的:我们 无法 在编译时知道它的长度(这就是它的 bind
实现的意义),因此它需要一个堆。正在开发一个用于 #![no_std] extern crate alloc;
的 alloc
功能,但它尚未完成。
依赖关系
~1.5–2.3MB
~41K SLoC