Как мне эмулировать Lisp (применить) или (карри) в Rust? - PullRequest
5 голосов
/ 14 февраля 2012

Я портирую QuickCheck на Rust, и я написал все, кроме for_all, поскольку я не уверен, какой должна быть подпись типа.

Я знаю, что вВообще, for_all примет свойство лямбда и набор лямбда-генератора.Он будет оценивать генераторы, чтобы создать случайный тестовый случай, чтобы свойство было введено в качестве входного.значения тестового примера.

Ответы [ 3 ]

7 голосов
/ 07 августа 2012

В Rust все функции принимают фиксированное количество параметров, поэтому в общем случае нет такой вещи, как эквивалент apply Лиспа, но макросы могут предоставить вам необходимую абстракцию.Вы можете написать:

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

Затем for_all!(|a, b| a + b, || 4, || 7) производит 11.

Удачи в вашем проекте!

2 голосов
/ 15 февраля 2012

Примечание редактора. Этот ответ относится к версии 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.См. руководство по Rust здесь для примера того, что делает typeState и каким образом оно способно обеспечить корректность программы.Насколько я понимаю, это в основном более мощная версия дизайна от Eiffel по контракту .

Обновление 2:

for_all принимаетодиночное свойство (например, is_even или divisible_by) и набор функций генератора.Генераторы - это лямбда-выражения, которые возвращают случайные значения для передачи в качестве входных данных в свойство, например, [gen_int] для is_even или [gen_int, gen_int] для divisible_by.for_all будет вызывать свойство, используя сгенерированные значения в качестве тестового примера, печатая +++ OK, прошло 100 тестов, если свойство возвращает true для 100 случайных тестовых случаев, или *** Failed!{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]];
}

Еще одна вещь: механизм расширения синтаксиса все еще довольно неполированный, поэтому невозможно импортировать макросы из разных ящиков.До этого момента определение #for_all должно появиться в файле, в котором оно вызывается.

0 голосов
/ 14 февраля 2012

Можете ли вы описать, что именно вы хотите? Я думаю, что вы просите что-то вроде этого:

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);
}
...