是否可以在运行时组合一系列函数?

时间:2017-10-18 08:11:50

标签: rust

我有3个功能:

fn f1() -> u64 {
    println!("Hello world: 1");
    2
}

fn f2(i: u64) -> Box<FnMut()> {
    println!("Hello world: {}", i);
    Box::new(|| println!("Hello world: {}", 3))
}

fn f3(mut f: Box<FnMut()>) {
    f()
}

一种功能技术是链接 - 将函数A的输出连接到函数B的输入:

fn main() {
    f3(f2(f1()));
}

这可能对Rust有帮助,因为这个方法纯粹是功能性的,功能可以是纯函数;他们不接触全局变量,只使用移动的论据(这很棒)。

如何在运行时执行此链?如果我的函数f4接受f2的输入但未将其用作f3。我们还可以通过向其添加返回类型来进一步链接:

fn f4(_: Box<FnMut()>) -> bool {
    println!("Hello world: 4");
    true
}

fn main() {
    f4(f2(f1())) // returns f4's result (true)
}

我希望能够决定如何在运行时链接我的函数。这个例子就是Lua代码(对不起):

function f1()
    print("Hello world: 1")
    return 2
end

function f2(args)
    print("Hello world: " .. args)
    return function()
        print("Hello world: " .. args + 1)
    end
end

function f3(args)
    args()
end

function f4()
    print("Hello world: 4")
end


function run_chain(list)
    local args
    for _, v in ipairs(list) do
        args = v(args)
    end
end

local list = {}
list[#list + 1] = f1
list[#list + 1] = f2
list[#list + 1] = f3
run_chain(list)
list[#list] = f4
run_chain(list)

这是脚本语言的动态类型的一大优点,但据我所知,Rust声称它比C ++更具功能性。是否可以以这种方式链接函数?

3 个答案:

答案 0 :(得分:3)

以下是如何处理简单链接问题的方法。将它从自由函数转换为构建器或运算符样式留作练习。它还使用&#34; impl Trait&#34; Rust 1.26中引入的功能使其更好。

fn f1(_: ()) -> u64 {
    println!("Hello world: 1");
    2
}
fn f2(i: u64) -> Box<FnMut()> {
    println!("Hello world: {}", i);
    Box::new(|| println!("Hello world: {}", 3))
}
fn f3(mut f: Box<FnMut()>) {
    f()
}
fn f4(_: Box<FnMut()>) -> bool {
    println!("Hello world: 4");
    true
}

fn dot<I, X, O, F1, F2>(mut f1: F1, mut f2: F2) -> impl FnMut(I) -> O
where
    F1: FnMut(I) -> X,
    F2: FnMut(X) -> O,
{
    move |i| f2(f1(i))
}

fn main() {
    let mut c = dot(dot(f1, f2), f3);
    c(());
    let mut c2 = dot(dot(f1, f2), f4);
    c2(());
}

Playground

将两个函数粘合在一起并不是很难,但如果类型更复杂,则可能会遇到生命周期问题。特别是,如果链中函数的输入参数是对前一个函数返回的类型的引用,则此代码将不会编译。我相信更多的参数和通用界限可以解决这个问题,但你必须尝试一下。

另请参阅tool cratecompose几乎就是我刚刚发布的内容)和rustz crate,两者都为Rust添加了更多功能的习惯用法。

答案 1 :(得分:1)

编辑:这个答案适用于我最初理解的问题。最后的链接评论使得这个答案不理想;那是一个不同的野兽。答案是,是的,这是可能的,但就像任何元编程都不容易。

不太好,但这与功能无关。这是打字。

你可以在Rust中执行此操作:

struct Chain {
    f1: Box<FnMut() -> u64>,
    f2: Box<FnMut(u64) -> Box<FnMut()>>,
    f3: Box<FnMut(Box<FnMut()>)>,
}

impl Chain {
    fn run(&self) {
        f3(f2(f1()));
    }
}

fn f1() -> u64 {
    println!("Hello world: 1");
    2
}

fn f2(i: u64) -> Box<FnMut()> {
    println!("Hello world: {}", i);
    Box::new(|| println!("Hello world: {}", 3))
}

fn f3(mut f: Box<FnMut()>) {
    f()
}

fn main() {
    let chain = Chain {
        f1: Box::new(f1),
        f2: Box::new(f2),
        f3: Box::new(f3),
    };
    chain.run();
}

但是你不能将任意函数附加到这个链上,也不能用f4代替f3

error[E0271]: type mismatch resolving `<fn(std::boxed::Box<std::ops::FnMut() + 'static>) -> bool {f4} as std::ops::FnOnce<(std::boxed::Box<std::ops::FnMut() + 'static>,)>>::Output == ()`
  --> src/main.rs:36:13
   |
36 |         f3: Box::new(f4),
   |             ^^^^^^^^^^^^ expected bool, found ()
   |
   = note: expected type `bool`
              found type `()`
   = note: required for the cast to the object type `std::ops::FnMut(std::boxed::Box<std::ops::FnMut() + 'static>)`

因为Rust是严格类型的,所以链中的函数必须是已知类型,并且这些类型必须适合。

也就是说,动态语言可以做任何事情,如果你自己实现足够的动态打字机制,Rust可以效仿。您可以创建一个包含Vec<Box<FnMut(&Any) -> Any>>add_func泛型方法的结构,该方法需要一些函数,并添加一个包装器,对Vec进行必要的展开,检查和重新包装。然后run方法按顺序调用这些函数。

答案 2 :(得分:-1)

我认为我实际上已经这样做了。此代码可能需要一些审核,但我认为一切都可以通过以下方式实现:

  1. 定义您想要调用的函数:

    fn f1() -> u64 {
        println!("Hello world: 1");
        2
    }
    fn f2(i: u64) -> Box<FnMut()> {
        println!("Hello world: {}", i);
        Box::new(|| println!("Hello world: {}", 3))
    }
    fn f3(mut f: Box<FnMut()>) {
        f()
    }
    fn f4(_: Box<FnMut()>) -> bool {
        println!("Hello world: 4");
        true
    }
    
  2. 利用Rust类型系统中的Any类型与闭包相同:

    use std::any::Any;
    
    struct Bridge<'a> {
        function: &'a mut FnMut(Box<Any>) -> Box<Any>,
    }
    

    然后,闭包可以用于被调用函数的类型擦除。但是,我们仍然需要在函数检查参数中做一些工作:

    fn run_chain(chain: &mut [Bridge]) -> Box<Any> {
        if chain.is_empty() {
            return Box::new(false)
        }
    
        let mut args;
        {
            let function = &mut chain.first_mut().unwrap().function;
            args = function(Box::new(0));
        }
        for c in chain.iter_mut().skip(1) {
            let res = (c.function)(args);
            args = res;
        }
        args
    }
    
    fn main() {
        let mut f1 = |_: Box<Any>| { let res = f1(); Box::new(res) as Box<Any> };
        let mut f2 = |args: Box<Any>| { let res = f2(*args.downcast::<u64>().unwrap()); Box::new(res) as Box<Any> };
        let mut f3 = |args: Box<Any>| { let res = f3(*args.downcast::<Box<FnMut()>>().unwrap()); Box::new(res) as Box<Any> };
        let mut f4 = |args: Box<Any>| { let res = f4(*args.downcast::<Box<FnMut()>>().unwrap()); Box::new(res) as Box<Any> };
        let mut fns: Vec<Bridge> = Vec::new();
        fns.push(Bridge { function: &mut f1 });
        fns.push(Bridge { function: &mut f2 });
        fns.push(Bridge { function: &mut f3 });
        let _ = run_chain(&mut fns);
        fns.pop();
        fns.push(Bridge { function: &mut f4 });
        let res = run_chain(&mut fns);
    
        println!("Result: {:?}", res.downcast::<bool>().unwrap());
    }
    
  3. 所以我们基本上所做的就是编写一个具有相同接口的闭包装器。在进一步传递参数之前,可以在闭包内部进行类型检查,并且可以检查它以便它不会导致崩溃。