4 个版本 (2 个破坏性更新)
0.3.1 | 2023 年 3 月 18 日 |
---|---|
0.3.0 | 2023 年 3 月 17 日 |
0.2.0 | 2023 年 3 月 17 日 |
0.1.0 | 2023 年 3 月 17 日 |
#2142 in Rust 模式
每月 36 次下载
63KB
1.5K SLoC
Rust 中的效果处理器。
受到 https://without.boats/blog/the-registers-of-rust/ 的启发,Rust 可以被认为是拥有三种著名的效应
- 异步
- 迭代
- 可失败性
目前在 Rust 中使用三种不同的特质来模拟这三种效应
"关键字泛型倡议"最近引起了一些争议,它提出了一些语法,允许我们以泛型方式将异步与其他特质组合起来。换句话说,你可以使用语法拥有一个"可能是异步的" Iterator
我觉得这个想法很有趣,但我认为语法引起的混淆比它有用的地方多。
我提出了 Effective
特质。正如我之前提到的,有三种效应。这个特质模拟了所有三种。你不需要 组合 效应,你可以 减去 效应。
例如,Future
是 Effective
- Iterator
- Try
impl<E> Future for Wrapper<E>
where
E: Effective<
// Fallibility is turned off
Failure = Infallible,
// Iteration is turned off
Produces = Single,
// Asynchrony is kept on
Async = Async
>,
{
type Output = E::Item;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
todo!()
}
}
着色问题
在 JS 生态系统中有一个著名的博客文章叫做 "你的函数是什么颜色?"。它声称 JS 中的异步函数与'非异步'函数颜色不同。我认为在这种情况下,'颜色'类比并不适用,因为颜色是混合的。
然而,它与 Effective
完美匹配。
// Red + Green + Blue
trait TryAsyncIterator = Effective<Failure = Error, Produces = Multiple, Async = Async>;
// Cyan (Blue + Green)
trait AsyncIterator = Effective<Failure = Infallible, Produces = Multiple, Async = Async>;
// Magenta (Red + Blue)
trait TryFuture = Effective<Failure = Error, Produces = Multiple, Async = Async>;
// Yellow (Green + Red)
trait TryIterator = Effective<Failure = Error, Produces = Multiple, Async = Blocking>;
// Red
trait Try = Effective<Failure = Error, Produces = Multiple, Async = Async>;
// Green
trait Iterator = Effective<Failure = Infallible, Produces = Multiple, Async = Blocking>;
// Blue
trait Future = Effective<Failure = Infallible, Produces = Single, Async = Async>;
// Black
trait FnOnce = Effective<Failure = Infallible, Produces = Single, Async = Blocking>;
示例
存在许多类似 map
的函数。例如,Iterator::map
、Option::map
、FuturesExt::map
、TryStreamExt::map_ok
。
它们的功能相同,即将成功值映射到其他成功值。《a href="https://docs.rs/effective/latest/effective/?search=effectiveext" title="`effectiveext`" rel="ugc noopener">EffectiveExt
也有一个 map
方法,但由于 Effective
可以模拟所有这些效果,它只需要一个方法。
Try
use effective::{impls::EffectiveExt, wrappers};
// an effective with just fallible set
let e = wrappers::fallible(Some(42));
let v: Option<i32> = e.map(|x| x + 1).try_get();
assert_eq!(v, Some(43));
未来
use effective::{impls::EffectiveExt, wrappers};
// an effective with just async set
let e = wrappers::future(async { 0 });
let v: i32 = e.map(|x| x + 1).shim().await;
迭代器
use effective::{impls::EffectiveExt, wrappers};
// an effective with just iterable set
let e = wrappers::iterator([1, 2, 3, 4]);
let v: Vec<i32> = e.map(|x| x + 1).collect().get();
assert_eq!(v, [2, 3, 4, 5]);
结合
use effective::{impls::EffectiveExt, wrappers};
async fn get_page(x: usize) -> Result<String, Box<dyn std::error::Error>> {
/* insert http request */
}
// an effective with just iterable set
let e = wrappers::iterator([1, 2, 3, 4])
.map(get_page)
// flatten in the async effect
.flatten_future()
// flatten in the fallible effect
.flatten_fallible();
let v: Vec<usize> = e.map(|x| x.len()).collect().unwrap().shim().await;
您也会注意到在最后一个示例中,我们使用了一个可失败的异步函数来 map
,并且我们可以使用 flat_map
+flatten_fallible
将输出直接嵌入到有效中。
这可以让您 添加 影响。
我们还可以 减去 影响,我们可以在 collect
方法中看到这一点,但还有更多。
// Effective<Failure = Infallible, Produces = Multiple, Async = Blocking>
let e = wrappers::iterator([1, 2, 3, 4]);
// still the same effects for now...
let e = e.map(get_page);
// Effective<Failure = Infallible, Produces = Multiple, Async = Async>
// We've flattened in in the 'async' effect.
let e = e.flatten_future();
// Effective<Failure = Box<dyn std::error::Error>, Produces = Multiple, Async = Async>
// We've flattened in in the 'fallible' effect.
let e = e.flatten_fallible();
let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
// Effective<Failure = Box<dyn std::error::Error>, Produces = Multiple, Async = Blocking>
// We've removed the async effect.
let e = e.block_on(runtime);
// Effective<Failure = Box<dyn std::error::Error>, Produces = Single, Async = Blocking>
// We've removed the iterable effect.
let e = e.collect();
// Effective<Failure = Infallible, Produces = Single, Async = Blocking>
// We've removed the fallible effect.
let e = e.unwrap();
// no more effects, just a single value to get
let e: Vec<_> = e.get();
北极星
显然,这个库相当复杂,难以理解。我认为它与关键字泛型是很好的搭配。
应该有一个语法来实现这些概念,但我认为底层特质是一个好的抽象。
类似于如何
这些语法元素可以组合起来制作应用级别的 Effective
实现。
async try get_page(after: Option<usize>) -> Result<Option<Page>, Error> { todo!() }
// `async` works as normal, allows the `.await` syntax
// `gen` allows the `yield` syntax, return means `Done`
// `try` allows the `?` syntax.
async gen try fn get_pages() -> Result<Page, Error> {
let Some(mut page) = get_page(None).await? else {
// no first page, exit
return;
};
loop {
let next_page = page.next_page;
// output the page (auto 'ok-wrapping')
yield page;
let Some(p) = get_page(Some(next_page)).await? else {
// no next page, exit
return;
};
page = p;
}
}
// This method is still `async` and `try`, but it removes the 'gen` keyword
// because internally we handle all the iterable effects.
async try fn save_pages() -> Result<(), Error> {
// The for loop is designed to handle iterable effects only.
// `try` and `await` here tell it to expect and propagate the
// fallible and async effects.
for try await page in get_pages() {
page.save_to_disk()?
}
}
使用适配器,它看起来像
let get_pages = wrappers::unfold(None, |next_page| {
wrappers::future(get_page(next_page)).flatten_fallible().map(|page| {
page.map(|| {
let next_page = page.next_page;
(page, Some(next_page))
})
})
});
let save_pages = get_pages.for_each(|page| {
wrappers::future(page.save_to_disk()).flatten_fallible()
});
依赖关系
~0.5–1.9MB
~33K SLoC