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 次下载

MIT 许可证

63KB
1.5K SLoC

Rust 中的效果处理器。

受到 https://without.boats/blog/the-registers-of-rust/ 的启发,Rust 可以被认为是拥有三种著名的效应

  • 异步
  • 迭代
  • 可失败性

目前在 Rust 中使用三种不同的特质来模拟这三种效应

"关键字泛型倡议"最近引起了一些争议,它提出了一些语法,允许我们以泛型方式将异步与其他特质组合起来。换句话说,你可以使用语法拥有一个"可能是异步的" Iterator

我觉得这个想法很有趣,但我认为语法引起的混淆比它有用的地方多。

我提出了 Effective 特质。正如我之前提到的,有三种效应。这个特质模拟了所有三种。你不需要 组合 效应,你可以 减去 效应。

例如,FutureEffective - 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::mapOption::mapFuturesExt::mapTryStreamExt::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();

北极星

显然,这个库相当复杂,难以理解。我认为它与关键字泛型是很好的搭配。

应该有一个语法来实现这些概念,但我认为底层特质是一个好的抽象。

类似于如何

  • async{}.await 模型一个 Future,
  • try{}? 模型一个 Try,
  • for/yield 模型一个 Iterator

这些语法元素可以组合起来制作应用级别的 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