我正在将QuickCheck移植到 Rust,并且我已经编写了所有内容,除了for_all
我不确定类型签名应该是什么。
我知道通常for_all
会接受一个属性 lambda 和一组生成器 lambda。它将评估生成器以创建随机测试用例以将属性作为输入。
如果属性返回 true,它应该打印+++ OK, passed 100 tests.
,否则,它应该打印*** Failed!
并打印有问题的测试用例值。
我正在将QuickCheck移植到 Rust,并且我已经编写了所有内容,除了for_all
我不确定类型签名应该是什么。
我知道通常for_all
会接受一个属性 lambda 和一组生成器 lambda。它将评估生成器以创建随机测试用例以将属性作为输入。
如果属性返回 true,它应该打印+++ OK, passed 100 tests.
,否则,它应该打印*** Failed!
并打印有问题的测试用例值。
在 Rust 中,所有函数都采用固定数量的参数,因此在一般情况下没有与 Lisp 等效的东西apply
,但宏可以为您提供所需的抽象。你可以写:
macro_rules! for_all {
( $tester:expr, $( $generator:expr ),* ) => {
$tester( $($generator() ),* )
}
}
然后,for_all!(|a, b| a + b, || 4, || 7)
产生11
.
祝你的项目好运!
编者注:此答案来自 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
必须出现在调用它的文件中。
你能准确描述你想要什么吗?我认为你要求的是这样的:
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);
}