如何在不创建闭包的情况下调用多参数函数?

How to invoke a multi-argument function without creating a closure?

我在 Rust 中做 2018 Advent of Code (Day 2, Part 1) 解决方案时遇到了这个问题。

要解决的问题:

将恰好有两个相同字母的字符串数乘以恰好有三个相同字母的字符串数。

输入

abcdega 
hihklmh 
abqasbb
aaaabcd

所以结果应该是:

2 个包含双字母(第一个和第三个)的字符串乘以 2 个包含三个字母(第二个和第三个)的字符串 = 4

问题:

const PUZZLE_INPUT: &str = 
"
abcdega
hihklmh
abqasbb
aaaabcd
";

fn letter_counts(id: &str) -> [u8;26] {
    id.chars().map(|c| c as u8).fold([0;26], |mut counts, c| { 
        counts[usize::from(c - b'a')] += 1;
        counts 
    })
}

fn has_repeated_letter(n: u8, letter_counts: &[u8;26]) -> bool {
    letter_counts.iter().any(|&count| count == n)
}

fn main() {
    let ids_iter = PUZZLE_INPUT.lines().map(letter_counts);
    let num_ids_with_double = ids_iter.clone().filter(|id| has_repeated_letter(2, id)).count();
    let num_ids_with_triple = ids_iter.filter(|id| has_repeated_letter(3, id)).count();
    println!("{}", num_ids_with_double * num_ids_with_triple);
}

Rust Playground

考虑行 21。函数 letter_counts 只接受一个参数,因此我可以使用语法:.map(letter_counts) 在与预期参数类型匹配的元素上。这对我来说真的很好!我喜欢不必创建闭包:.map(|id| letter_counts(id))。我发现两者都可读,但没有关闭的前一个版本对我来说更清晰。

现在考虑行 2223。在这里,我必须使用语法:.filter(|id| has_repeated_letter(3, id)) 因为 has_repeated_letter 函数有两个参数。我真的很想 .filter(has_repeated_letter(3)) 代替。

当然,我可以让函数取一个元组,映射到一个元组并只使用一个参数……但这似乎是一个糟糕的解决方案。我宁愿只创建闭包。

省略 only 参数是 Rust 允许你做的事情。为什么编译器更难让你省略 last 参数,前提是它具有接受 [=36] 的函数的所有其他 n-1 参数=] 参数。

我觉得这会使语法更清晰,并且更适合 Rust 喜欢的惯用函数式风格。

我当然不是编译器专家,但实现此行为似乎很简单。如果我的想法不正确,我很想知道更多为什么会这样。

不,您不能将具有多个参数的函数作为隐式闭包传递。

在某些情况下,您可以选择使用 currying 来减少函数的 arity。例如,这里我们将 add 函数从 2 个参数减少到一个:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn curry<A1, A2, R>(f: impl FnOnce(A1, A2) -> R, a1: A1) -> impl FnOnce(A2) -> R {
    move |a2| f(a1, a2)
}

fn main() {
    let a = Some(1);
    a.map(curry(add, 2));
}

但是,我同意这不是好处的评论:

  1. 打字也没少:

    a.map(curry(add, 2));
    a.map(|v| add(v, 2));
    
  2. curry功能极其有限:它选择使用FnOnce,但FnFnMut也有用例。它仅适用于具有两个参数的函数。

但是,I have used这个高阶函数技巧在其他项目中,添加的代码量要大得多。