【问题标题】:How to invoke a multi-argument function without creating a closure?如何在不创建闭包的情况下调用多参数函数?
【发布时间】:2019-05-17 07:30:46
【问题描述】:

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

要解决的问题:

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

输入

abcdega 
hihklmh 
abqasbb
aaaabcd
  • 第一个字符串abcdega 重复了两次a
  • 第二个字符串hihklmhh 重复了3 次。
  • 第三个字符串 abqasbba 重复两次,b 重复了 3 次,所以两者都计算在内。
  • 第四个字符串aaaabcd 包含一个重复4 次的字母(不是23),因此不算数。

所以结果应该是:

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 参数,前提是它具有所有其他 n-1 参数,用于接受 n 参数的函数。

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

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

【问题讨论】:

  • 你想要做的是“currying”或“partial function application”,这在 Rust 中不直接支持。编写 lambda 实际上是这样做的惯用方式。
  • 想象一个函数fn foo(a: i32, b: i32) {...},然后你称之为fn main() { foo(4); },根据你的提议,这段代码是合法的,但什么也不做。恕我直言,在真正需要时编写诸如|x| foo(4, x) 之类的lambda 更为合理。您也可以轻松编写|x| foo(x, 4) 或具有任意数量参数的任何其他变体,而不会影响语言的其余部分。
  • 当然你可以编写一个宏来接受这些参数并扩展为一个 lambda,但是,apply!(has_repeated_letter, 3)|x| has_repeated_letter(3, x) 有什么优势?
  • 你能不能改变 has_repeated_letter 来返回像this这样的闭包
  • 对于您的下一个问题,请考虑减少绒毛量。您的问题基本上归结为“我可以做到这一点[一个论点示例],我该怎么做[两个论点示例]”背景的数量(代码的出现基本上与问题完全无关)将使这个问题变得很多其他人更难阅读它以了解当他们有潜在的重复时它是否与他们相关。

标签: functional-programming rust iterator currying


【解决方案1】:

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

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

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

但是,我同意 cmets 的观点,即这不是好处:

  1. 打字也不少:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-10
    • 2018-07-13
    • 2016-12-23
    • 1970-01-01
    • 2017-12-25
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    相关资源
    最近更新 更多