【问题标题】:How do I emulate Lisp (apply) or (curry) in Rust?如何在 Rust 中模拟 Lisp(应用)或(咖喱)?
【发布时间】:2012-02-14 04:57:53
【问题描述】:

我正在将 QuickCheck 移植到 Rust,我已经编写了除 for_all 之外的所有内容,因为我不确定类型签名应该是什么。

我知道,for_all 通常会接受一个属性 lambda 和一组生成器 lambda。它将评估生成器以创建随机测试用例以将属性作为输入。

如果属性返回 true,它应该打印 +++ OK, passed 100 tests.,否则,它应该打印 *** Failed! 并打印有问题的测试用例值。

【问题讨论】:

    标签: testing quickcheck rust


    【解决方案1】:

    在 Rust 中,所有函数都采用固定数量的参数,因此一般情况下不存在与 Lisp 的 apply 等效的东西,但宏可以为您提供所需的抽象。你可以写:

    macro_rules! for_all {
        ( $tester:expr, $( $generator:expr ),* ) => {
            $tester( $($generator() ),* )
        }
    }
    

    然后,for_all!(|a, b| a + b, || 4, || 7) 产生 11

    祝你的项目好运!

    【讨论】:

    • 感谢您的精彩代码!我需要的最后一件事是 1) 将生成的值临时存储在某种集合中 2) 将测试器函数应用于集合中的每个测试用例 3) 如果任何测试用例失败,则打印“FAIL!”并打印该测试用例中的值 4) 循环步骤 1-3,以便针对测试仪运行 100 个测试用例,如果任何测试用例失败则停止。如果 100 个测试用例没有一个失败,则打印“Ok”。帮助我欧比旺卡诺比,你是我唯一的希望。
    【解决方案2】:

    编者注:此答案来自 Rust 1.0 之前的版本,并且包含在 Rust 1.0 中语法无效的代码。

    如果您只需要一种定义 apply 的方法,请尝试 Rust 的宏示例语法扩展:

    fn main() {
        #macro[[#apply[f, [x, ...]], f(x, ...)]];
    
        fn add(a: int, b: int) -> int { a + b }
    
        assert (#apply[add, [1, 15]] == 16);
    }
    

    以上代码来自the Rust test suite

    遗憾的是,目前有关语法扩展的文档有点稀少。 Rust reference manual 可能是您最好的选择——尽管它给出的示例(apply,不少于!)已经过时,所以我不确定它的信息有多少是可信的。

    更新:

    剩下的就是弄清楚如何将 add ... assert 包装在一个具有正确类型签名的函数中,该函数接受任意生成器。

    我仍然不确定你是如何将这一切放在一起的,但这里有一个函数可以接受任何产生 int 的函数:

    use std;
    import std::rand;
    
    fn assert_even(num_gen: fn() -> int) -> (bool, int) {
        let num = num_gen();
        ret (num % 2 == 0, num);
    }
    
    fn main() {
        let rng = rand::mk_rng();
    
        let gen_even = {|| (rng.next() as int) * 2};
    
        log(error, assert_even(gen_even));
    }
    

    然而,目前在 Rust 中使用数字有点痛苦,如果你想将 assert_even 推广到任何数字类型,你必须定义接口/实现,然后用有界声明 assert_even泛型:

    use std;
    import std::rand;
    
    iface is_even { fn is_even() -> bool; }
    
    impl of is_even for int { fn is_even() -> bool { self % 2 == 0 } }
    
    impl of is_even for u32 { fn is_even() -> bool { self % 2u == 0u } }
    
    fn assert_even<T: is_even>(num_gen: fn() -> T) -> (bool, T) {
        let num = num_gen();
        ret (num.is_even(), num);
    }
    
    fn main() {
        let rng = rand::mk_rng();
    
        let gen_even_int = {|| (rng.next() as int) * 2};
        let gen_even_u32 = {|| rng.next() * 2u};
    
        log(error, assert_even(gen_even_int));
        log(error, assert_even(gen_even_u32));
    }
    

    旁注:如果您对测试感兴趣,您应该查看 Rust 的类型状态工具。请参阅the Rust manual here 了解 typestate 的作用以及它能够强制执行程序正确性的方式的示例。按照我的理解,它基本上是 Eiffel 的design by contract 的一个更强大的版本。

    更新 2:

    for_all 接受单个属性(例如 is_even 或 divisible_by)和生成器函数的集合。生成器是 lambdas,它返回随机值作为输入传递给属性,例如[gen_int] 代表 is_even 或 [gen_int, gen_int] 代表 divisible_by。 for_all 将使用生成的值作为测试用例调用属性,打印 +++ OK,如果属性为 100 个随机测试用例返回 true,则通过 100 次测试,或者 *** 失败! {test_case} 如果其中一个测试用例失败。

    这个完整的源文件应该完全展示了您正在寻找的行为,希望(for_all 的定义接近最底部):

    use std;
    import std::rand;
    import std::io::println;
    
    iface is_even { fn is_even() -> bool; }
    
    impl of is_even for int { fn is_even() -> bool { self % 2 == 0 } }
    
    fn main() {
        let rng = rand::mk_rng();
    
                                       // Cast to int here because u32 is lame
        let gen_even = {|| (rng.next() as int) * 2};
        let gen_float = {|| rng.next_float()};
    
        // Accepts generators that produce types that implement the is_even iface
        fn assert_even<T: is_even>(num_gen: fn() -> T) -> bool {
            let num = num_gen();
            let prop_holds = num.is_even();
            if !prop_holds {
                println(#fmt("Failure: %? is not even", num));
            }
            ret prop_holds;
        }
    
        fn assert_divisible(num_gen1: fn() -> float,
                            num_gen2: fn() -> float) -> bool {
            let dividend = num_gen1(),
                divisor = num_gen2();
            let prop_holds = dividend / divisor == 0f;
            if !prop_holds {
                println(#fmt("Failure: %? are not divisible", (dividend, divisor)));
            }
            ret prop_holds;
        }
    
                                            // Begin anonymous closure here
        #macro[[#for_all[prop, [gen, ...]], {||
            let passed_tests = 0;
            let prop_holds = true;
            // Nice iterators and break/continue are still being implemented,
            // so this loop is a bit crude.
            while passed_tests < 100 && prop_holds {
                prop_holds = prop(gen, ...);
                if prop_holds { passed_tests += 1; }
            }
            println(#fmt("Tests passed: %d", passed_tests));
            ret 0;  // Necessary to infer type of #for_all, might be a compiler bug
        }()]];  // Close anonymous closure and self-execute, then close #macro
    
        #for_all[assert_even, [gen_even]];
        #for_all[assert_divisible, [gen_float, gen_float]];
    }
    

    还有一点:语法扩展机制还很不完善,因此无法从不同的 crate 中导入宏。在此之前,#for_all 的定义必须出现在调用它的文件中。

    【讨论】:

    • 这非常有帮助!剩下的就是弄清楚如何将add ... assert 包装在一个具有接受任意生成器的正确类型签名的函数中。代替115,生成随机数(或任何类型)的函数。
    • @mcandre 您能否举一个for_all 调用的示例以及您希望传入的函数的签名?
    • 访问wiki.call-cc.org/eggref/4/cluckcheck并找到或滚动到“for-all”。
    • @mcandre 我正在编辑帖子,就像您发表最后一条评论一样,这是否回答了您的问题?立即查看您的链接。
    • 酷。你能重写这个例子,让for_all 接受任意返回类型的生成器吗?不仅是 int,不仅是数字,还有字符串、向量等。不仅是一元属性,还有 n 元属性。
    【解决方案3】:

    你能准确描述你想要什么吗?我认为你要求的是这样的:

    fn for_all<A>(test: fn(A) -> bool, generators: &[fn() -> A]) -> bool {
        generators.iter().all(|gen| test(gen()))
    }
    
    fn main() {
        let generators: Vec<fn() -> (i32, i32)> = vec![
            || (1, 2),
            || (2, 3),
            || (3, 4),
        ];
    
        for_all(|(a, b)| a < b, &generators);
    }
    

    【讨论】:

    • 不完全。我很难解释这一点。 yellosoft.us/quickcheck 做得更好。如果你能在 Rust 中重现 Lisp 的 (apply),剩下的我可以做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-26
    • 2018-02-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多