我正在将QuickCheck移植到Rust,我写了除for_all
之外的所有内容,因为我不确定类型签名应该是什么。
我知道,一般来说,for_all
将接受属性lambda和生成器lambda的集合。它将评估生成器以创建随机测试用例以将属性作为输入。
如果属性返回true,它应该打印+++ OK, passed 100 tests.
,否则,它应该打印*** Failed!
并打印有问题的测试用例值。
答案 0 :(得分:7)
在Rust中,所有函数都使用固定数量的参数,因此在一般情况下没有等同于Lisp的apply
,但宏可以为您提供所需的抽象。你可以写:
macro_rules! for_all {
( $tester:expr, $( $generator:expr ),* ) => {
$tester( $($generator() ),* )
}
}
然后,for_all!(|a, b| a + b, || 4, || 7)
生成11
。
祝你的项目好运!
答案 1 :(得分:2)
编者注:这个答案来自1.0之前的Rust版本,包含在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
推广到任何数字类型,你必须定义接口/实现然后声明{{1}使用有界泛型类型:
assert_even
附注:如果您对测试感兴趣,您应该查看Rust的typestate设施。有关typestate的作用以及它能够强制执行程序正确性的方法,请参阅the Rust manual here。我理解它的方式,它基本上是埃菲尔design by contract的更强大版本。
更新2:
for_all接受单个属性(例如is_even或divisible_by)和一组生成器函数。生成器是lambda,它返回随机值以作为输入传递给属性,例如, [gen_int]表示is_even或[gen_int,gen_int]表示divisible_by。 for_all将使用生成的值作为测试用例调用属性,打印+++ OK,如果属性为100个随机测试用例返回true,则通过100次测试,或***失败! {test_case}如果其中一个测试用例失败。
这个完整的源文件应该充分展示您正在寻找的行为,希望(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));
}
的定义接近最底层):
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]];
}
的定义必须出现在调用它的文件中。
答案 2 :(得分: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);
}