簡體   English   中英

Rust 中的函數組合鏈

[英]Function composition chain in Rust

我想在 Rust 的對象方法鏈中實現函數組合

在此之前,我確認在 Rust 中可以為函數應用的“管道”實現對象方法鏈,如下所示:

https://docs.rs/apply/0.2.2/apply/trait.Apply.html

trait Pipe: Sized {
    fn pipe<F, B>(self, f: F) -> B
    where
        F: FnOnce(Self) -> B,
    {
        f(self)
    }
}

impl<T> Pipe for T {}
 
fn main() {
    let string = 1.pipe(|x| x * 2).pipe(|x| x + 1).pipe(|x| x.to_string());

    println!("{}", string);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d6210a499e2522ff04e0cbbeef8cedc5


現在,函數組合的代碼是如何在 Rust 中組合函數?

fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |x| g(f(x))
}

fn main() {
    let multiply_and_add = compose(|x| x * 2, |x| x + 2);
    let divide_and_subtract = compose(|x| x / 2, |x| x - 2);

    let finally = compose(multiply_and_add, divide_and_subtract);
    println!("Result is {}", finally(10));
}

所以目標是

let composed_f = (|x| x * 2).compose(|x| x + 1).compose(|x| x.to_string());

在像“管道”這樣的 Rust 對象方法鏈中實現函數組合的正確代碼是什么?


編輯

https://stackoverflow.com/a/45792463/11316608引用的函數組合宏

macro_rules! compose {
    ( $last:expr ) => { $last };
    ( $head:expr, $($tail:expr), +) => {
        compose_two($head, compose!($($tail),+))
    };
}

fn compose_two<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |x| g(f(x))
}

fn main() {
    let add = |x| x + 2;
    let multiply = |x| x * 2;
    let divide = |x| x / 2;
    let intermediate = compose!(add, multiply, divide);

    let subtract = |x| x - 2;
    let finally = compose!(intermediate, subtract);

    println!("Result is {}", finally(10));
}

直接在閉包上實現這一點很棘手,但我們可以使用表示可組合函數的新類型使其變得更加簡單。 這增加了一個轉換為可組合函數然后在最后再次輸出的步驟,但它最終相當合理(特別是考慮到它的表達能力),它不需要裝箱或其他巧妙的技巧,並且實現非常容易理解.

因為它以未裝箱的嵌套閉包調用結束,所以編譯器也很可能能夠優化所有組合調用並將閉包彼此內聯,甚至可能進入最終調用站點。

use std::marker::PhantomData;

struct ComposeableFn<F, A, B>(F, PhantomData<*mut A>, PhantomData<*mut B>);

impl<F, A, B> ComposeableFn<F, A, B>
    where F: FnMut(A) -> B
{
    pub fn new(f: F) -> Self {
        Self(f, Default::default(), Default::default())
    }
    
    pub fn compose<C>(self, mut next: impl FnMut(B) -> C)
        -> ComposeableFn<impl FnMut(A) -> C, A, C>
    {
        let mut prior = self.0;
        
        ComposeableFn::new(move |v| next(prior(v)))
    }
    
    pub fn into_inner(self) -> F {
        self.0
    }
}

像這樣使用:

fn main() {
    let mut composed_f = ComposeableFn::new(|x: i32| x * 2)
        .compose(|x| x + 1)
        .compose(|x| x.to_string())
        .into_inner();
        
    println!("{:?}", composed_f(21)); // "43"
}

游樂場


PhantomData成員需要允許實現命名AB ,否則它們將被視為未使用。 我選擇了*mut _因為這使得AB中的類型不變。


您可以使用宏使合成的表達更好一些:

macro_rules! compose {
    ({ $fh:expr } $( { $ft:expr } )*) => {
        ComposeableFn::new($fh)
        $( .compose($ft) )*
        .into_inner()
    }
}

fn main() {
    let mut composed_f = compose!(
        {|x: i32| x * 2}
        {|x| x + 1}
        {|x| x.to_string()}
    );
        
    println!("{:?}", composed_f(21));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM