我正在编写一些代码,其中我对延迟求值函数链感兴趣。换句话说,它存储您想要的所有操作,并且只将它们一起求值。
当链中的所有函数都采用相同的类型并返回相同的类型时,这非常容易。然而,当函数链每次返回不同的类型时,我一直纠结于如何使其工作。这种简单的情况可以在以下代码中看到:
struct FuncChain<T> {
funcs: Vec<fn(T) -> T>
}
impl<T> FuncChain<T> {
fn call(&self, input: T) -> T {
self.funcs.iter().fold(input, |prev, func| func(prev))
}
}
fn main(){
let fc = FuncChain {
funcs: vec![
|x| x + 1,
|x| x + 2,
|x| x * 2,
|x| x - 2,
]
};
println!("{}", fc.call(1));
}
(游乐场(
在这种情况下,我们使用i32 -> i32 -> i32 -> i32 -> i32
。
我想做的是一个更一般的情况,我们去A -> B -> C -> D -> E
,这意味着funcs
向量包含:fn(A) -> B
、fn(B) -> C
、fn(C) -> D
和fn(D) -> E
。但是,如何将此类型定义分配给结构?我不能创建具有异构类型的向量,即使我可以,结构的类型签名会是什么?
也许我可以做一个递归类型定义,其中FuncChain
保存一个指向第一个函数对象的指针,以及链中的下一个对象:
struct FuncChain<S, T, U> {
func: fn(S) -> T,
next: FuncChain<T, U, ?>
}
impl<S, T, U> FuncChain<S, T, U> {
fn call(&self, input: T) -> T {
self.funcs.iter().fold(input, |prev, func| func(prev))
}
}
fn main(){
let fc = FuncChain {
funcs: vec![
|x| x.toString(),
|x| u8::fromStr(x),
|x| x.toString(),
|x| i32::fromStr(x),
]
};
println!("{}", fc.call(1));
}
当然,这是行不通的,因为我不知道next
的输出类型。
如何做到这一点?
你的问题类似于Iterator
,因此可以用相同的解决方案来解决:一个特征表示一个"可调用";。
这种特质让你"中断";您当前基于结构的系统的无限递归,通过使结构仅将其表示为";不管它做什么";。
https://play.rust-lang.org/?version=stable&mode=调试&edition=2021&gist=f0d6bcc9eb8e070c1d9b6469f6a5e148
struct Chain<U, V, F> {
prev: F,
f: fn(U) -> V,
}
trait FuncChain<T, U> {
fn call(&self, _: T) -> U;
fn chain<V>(self, next: fn(U) -> V) -> Chain<U, V, Self>
where
Self: Sized,
{
Chain {
prev: self,
f: next,
}
}
}
impl<T, U> FuncChain<T, U> for fn(T) -> U {
fn call(&self, t: T) -> U {
self(t)
}
}
impl<T, U, V, F> FuncChain<T, V> for Chain<U, V, F>
where
F: FuncChain<T, U>,
{
fn call(&self, t: T) -> V {
(self.f)(self.prev.call(t))
}
}
fn main() {
let c = ((|x| x + 1) as fn(i32) -> i32)
.chain(|x| x * 2)
.chain(|x| x - 2);
println!("{}", c.call(5));
}
一个更好的Rustacean可能会设计一种更简单的方法来实现这一点。
如果你对夜间使用很满意,那么可能有一种方法可以使用Fn
,而不需要自定义特性。
见鬼,从根本上说,它只是.
,所以你可能只需要一个通用函数和一个闭包就可以管理,我必须检查一下。