7

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

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

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

4

3 回答 3

7

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

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

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

祝你的项目好运!

于 2012-08-06T20:04:42.953 回答
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);
}

上面的代码来自Rust 测试套件

可悲的是,目前关于语法扩展的文档有点稀少。Rust 参考手册可能是你最好的选择——尽管它给出的示例(至少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 的类型状态工具。有关typestate 的作用以及它能够强制执行程序正确性的方式的示例,请参见此处的 Rust 手册。按照我的理解,它基本上是埃菲尔合同设计的更强大的版本。

更新 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必须出现在调用它的文件中。

于 2012-02-15T15:39:58.857 回答
0

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

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);
}
于 2012-02-14T18:22:39.207 回答