[英]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);
}
現在,函數組合的代碼是如何在 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
成員需要允許實現命名A
和B
,否則它們將被視為未使用。 我選擇了*mut _
因為這使得A
和B
中的類型不變。
您可以使用宏使合成的表達更好一些:
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.