调用一个函数,该函数使用不同的闭包进行两次闭包

时间:2015-01-28 20:28:30

标签: rust parser-combinators

作为学习生锈的项目,我正在编写一个可以解析sgf文件的程序(一种用于存储游戏的格式,以及技术上还有其他游戏)。目前该程序应该解析该类型的字符串(这只是一个例子)";B[ab]B[cd]W[ef]B[gh]"[Black((0,1)),Black((2,3,)),White((4,5)),Black((6,7))]

为此我使用的是parser-combinators库。

我遇到了以下错误:

main.rs:44:15: 44:39 error: can't infer the "kind" of the closure; explicitly annotate it; e.g. `|&:| {}` [E0187]
main.rs:44      pmove().map(|m| {Property::White(m)})
                            ^~~~~~~~~~~~~~~~~~~~~~~~
main.rs:44:15: 44:39 error: mismatched types:
 expected `closure[main.rs:39:15: 39:39]`,
    found `closure[main.rs:44:15: 44:39]`
(expected closure,
    found a different closure) [E0308]
main.rs:44      pmove().map(|m| {Property::White(m)})
                            ^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `go`.

有问题的功能如下。我是生锈的新手,所以我无法真正隔离问题,或者在没有解析器 - 组合器库的情况下重新创建它(甚至可能与该库有关系吗?)。

fn parse_go_sgf(input: &str) -> Vec<Property> {
    let alphabetic = |&:| {parser::satisfy(|c| {c.is_alphabetic()})};
    let prop_value = |&: ident, value_type| {
        parser::spaces().with(ident).with(parser::spaces()).with(
            parser::between(
                parser::satisfy(|c| c == '['),
                parser::satisfy(|c| c == ']'),
                value_type
            )
        )
    };

    let pmove = |&:| {
        alphabetic().and(alphabetic())
        .map(|a| {to_coord(a.0, a.1)})
    };

    let pblack = prop_value(
        parser::string("B"),
        pmove().map(|m| {Property::Black(m)}) //This is where I am first calling the map function.
    );

    let pwhite = prop_value(
        parser::string("W"),
        pmove().map(|m| {Property::White(m)}) //This is where the compiler complains
    );

    let pproperty = parser::try(pblack).or(pwhite);

    let mut pnode = parser::spaces()
        .with(parser::string(";"))
        .with(parser::many(pproperty));

    match pnode.parse(input) {
        Ok((value, _)) => value,
        Err(err) => {
            println!("{}",err);
            vec!(Property::Unkown)
        }
    }
}

所以我猜这与所有具有不同类型的闭包有关。但在其他情况下,似乎可以使用不同的闭包调用相同的函数。例如

let greater_than_forty_two = range(0, 100)
                         .find(|x| *x > 42);
let greater_than_forty_three = range(0, 100)
                         .find(|x| *x > 43);

似乎工作正常。

所以在我的情况下发生了什么不同。

此外,正如我刚刚学习的那样,对代码的任何一般性评论也是受欢迎的。

4 个答案:

答案 0 :(得分:4)

不幸的是,你偶然发现了Rust类型系统中的一个粗糙边缘(鉴于解析器 - 组合器的封闭性质,并非真正意外)。

以下是您的问题的简化示例:

fn main() {
    fn call_closure_fun<F: Fn(usize)>(f: F) { f(12) }  // 1
    fn print_int(prefix: &str, i: usize) { println!("{}: {}", prefix, i) }

    let call_closure = |&: closure| call_closure_fun(closure);  // 2

    call_closure(|&: i| print_int("first", i));  // 3.1
    call_closure(|&: i| print_int("second", i)); // 3.2
}

它提供与您的代码完全相同的错误:

test.rs:8:18: 8:47 error: mismatched types:
 expected `closure[test.rs:7:18: 7:46]`,
    found `closure[test.rs:8:18: 8:47]`
(expected closure,
    found a different closure) [E0308]
test.rs:8     call_closure(|&: i| print_int("second", i));
                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我们(在代码中的注释中引用):

  1. 接受某种具体形式的封闭的功能;
  2. 一个闭包,它用自己的参数调用函数(1);
  3. 两次调用closure(1),每次都传递不同的闭包。
  4. Rust关闭是未装箱的。这意味着对于每个闭包,编译器生成一个新类型,它实现一个闭包特征(FnFnMutFnOnce)。这些类型是匿名的 - 它们没有您可以写出的名称。你所知道的是这些类型实现了某种特性。

    Rust是一种强类和静态类型语言:编译器必须在编译时知道每个变量和每个参数的确切类型。因此,它必须为您编写的每个闭包的每个参数分配类型。但是(2)的closure参数应该具有什么类型?理想情况下,它应该是一些泛型类型,就像在(1)中一样:只要它实现了特征,闭包就应该接受任何类型。但是,Rust闭包不能是通用的,因此没有语法来指定它。所以Rust编译器做了最自然的事情 - 它根据closure的第一次使用推断call_closure参数的类型,即来自3.1调用 - 也就是说,它指定了匿名3.1中的闭包类型!<​​/ p>

    但是这个匿名类型与3.2中的闭包的匿名类型不同:它们唯一的共同点是它们都实现了Fn(usize)。这正是错误所在。

    最好的解决方案是使用函数而不是闭包,因为函数可以是通用的。不幸的是,你也无法做到这一点:你的闭包返回内部包含闭包的结构,比如

    pub struct Satisfy<I, Pred> { ... }
    

    其中Pred后来被约束为Pred: FnMut(char) -> bool。同样,因为闭包具有匿名类型,所以不能在类型签名中指定它们,因此您将无法写出此类泛型函数的签名。

    事实上,以下工作确实有效(因为我已经为parser::satisfy()调用参数提取了闭包):

    fn prop_value<'r, I, P, L, R>(ident: I, value_type: P, l: L, r: R) -> pp::With<pp::With<pp::With<pp::Spaces<&'r str>, I>, pp::Spaces<&'r str>>, pp::Between<pp::Satisfy<&'r str, L>, pp::Satisfy<&'r str, R>, P>>
        where I: Parser<Input=&'r str, Output=&'r str>,
              P: Parser<Input=&'r str, Output=Property>,
              L: Fn(char) -> bool,
              R: Fn(char) -> bool {
        parser::spaces().with(ident).with(parser::spaces()).with(
            parser::between(
                parser::satisfy(l),
                parser::satisfy(r),
                value_type
            )
        )
    }
    

    你会像这样使用它:

    let pblack = prop_value(
        parser::string("B"),
        pmove().map(|&: m| Property::Black(m)),
        |c| c == '[', |c| c == ']'
    );
    
    let pwhite = prop_value(
        parser::string("W"),
        pmove().map(|&: m| Property::White(m)),
        |c| c == '[', |c| c == ']'
    );
    

    ppuse parser::parser as pp一起推出。

    这确实有效,但实际上很难看 - 我不得不使用编译器错误输出来实际确定所需的返回类型。随着功能的微小变化,它将不得不再次调整。理想情况下,这是通过未装箱的抽象返回类型解决的 - 它们上有a postponed RFC - 但我们还没有。

答案 1 :(得分:3)

作为解析器组合器的作者,我将简单介绍另一种解决方法,而无需使用编译器生成返回类型。

由于每个解析器基本上只是一个函数和2个相关类型,因此所有函数类型都有Parser特征的实现。

impl <I, O> Parser for fn (State<I>) -> ParseResult<O, I>
    where I: Stream { ... }
pub struct FnParser<I, O, F>(F);
impl <I, O, F> Parser for FnParser<I, O, F>
    where I: Stream, F: FnMut(State<I>) -> ParseResult<O, I> { ... }

一旦孤儿检查允许,这些都应该被单个特征替换并移除FnParser类型。与此同时,我们可以使用FnParser类型从闭包中创建解析器。

使用这些特征,我们基本上可以隐藏Vladimir Matveev的例子中返回的大解析器类型。

fn prop_value<'r, I, P, L, R>(ident: I, value_type: P, l: L, r: R, input: State<&'r str>) -> ParseResult<Property, &'r str>
    where I: Parser<Input=&'r str, Output=&'r str>,
      P: Parser<Input=&'r str, Output=Property>,
      L: Fn(char) -> bool,
      R: Fn(char) -> bool {
    parser::spaces().with(ident).with(parser::spaces()).with(
        parser::between(
            parser::satisfy(l),
            parser::satisfy(r),
            value_type
        )
    ).parse_state(input)
}

我们现在可以使用此

构造解析器
let parser = FnParser(move |input| prop_value(ident, value_type, l, r, input));

这基本上是我们目前使用生锈所能做的最好的事情。未装箱的匿名返回类型会使所有这些变得非常容易,因为不需要复杂的返回类型(也不会创建,因为可以编写库本身来利用它,完全避免使用复杂类型)。

答案 2 :(得分:2)

Rust的闭包的两个方面导致了你的问题,一个,闭包不能是通用的,两个,每个闭包都是它自己的类型。由于closure不能是通用的,prop_value的参数value_type必须是特定类型。因为每个闭包都是特定类型,所以传递给prop_value中的pwhite的闭包与pblack中的闭包不同。编译器所做的是得出结论:value_type必须具有pblack中闭包的类型,当它到达pwhite时,它会找到不同的闭包,并给出错误。

从您的代码示例来看,最简单的解决方案可能是使prop_value成为通用fn - 看起来它不需要是一个闭包。或者,您可以将其参数value_type声明为闭包特征对象,例如&Fn(...) -> ...。这是一个简化的例子,展示了这些方法:

fn higher_fn<F: Fn() -> bool>(f: &F) -> bool {
    f()
}
let higher_closure = |&: f: &Fn() -> bool | { f() };

let closure1 = |&:| { true };
let closure2 = |&:| { false };
higher_fn(&closure1);
higher_fn(&closure2);
higher_closure(&closure1);
higher_closure(&closure2);

答案 3 :(得分:1)

现有的答案很好,但我想分享一个更小的问题例子:

fn thing<F: FnOnce(T), T>(f: F) {}

fn main() {
    let caller = |&: f| {thing(f)};
    caller(|&: _| {});
    caller(|&: _| {});
}

当我们定义caller时,其签名尚未完全修复。当我们第一次调用它时,类型推断设置输入和输出类型。在此示例中,在第一次调用之后,将需要caller来获取具有特定类型的闭包,即第一个闭包的类型。这是因为每个闭包都有自己独特的匿名类型。当我们第二次调用caller时,第二个闭包的(唯一的,匿名的)类型不合适!

正如@wingedsubmariner指出的那样,没有办法用泛型类型创建闭包。如果我们有像for<F: Fn()> |f: F| { ... }这样的假设语法,那么也许我们可以解决这个问题。制作通用功能的建议很好。