3

I came across this while doing the 2018 Advent of Code (Day 2, Part 1) solution in Rust.

The problem to solve:

Take the count of strings that have exactly two of the same letter, multiplied by the count of strings that have exactly three of the same letter.

INPUT

abcdega 
hihklmh 
abqasbb
aaaabcd
  • The first string abcdega has a repeated twice.
  • The second string hihklmh has h repeated three times.
  • The third string abqasbb has a repeated twice, and b repeated three times, so it counts for both.
  • The fourth string aaaabcd contains a letter repeated 4 times (not 2, or 3) so it does not count.

So the result should be:

2 strings that contained a double letter (first and third) multiplied by 2 strings that contained a triple letter (second and third) = 4

The Question:

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

Consider line 21. The function letter_counts takes only one argument, so I can use the syntax: .map(letter_counts) on elements that match the type of the expected argument. This is really nice to me! I love that I don't have to create a closure: .map(|id| letter_counts(id)). I find both to be readable, but the former version without the closure is much cleaner to me.

Now consider lines 22 and 23. Here, I have to use the syntax: .filter(|id| has_repeated_letter(3, id)) because the has_repeated_letter function takes two arguments. I would really like to do .filter(has_repeated_letter(3)) instead.

Sure, I could make the function take a tuple instead, map to a tuple and consume only a single argument... but that seems like a terrible solution. I'd rather just create the closure.

Leaving out the only argument is something that Rust lets you do. Why would it be any harder for the compiler to let you leave out the last argument, provided that it has all of the other n-1 arguments for a function that takes n arguments.

I feel like this would make the syntax a lot cleaner, and it would fit in a lot better with the idiomatic functional style that Rust prefers.

I am certainly no expert in compilers, but implementing this behavior seems like it would be straightforward. If my thinking is incorrect, I would love to know more about why that is so.

12
  • What you want to do is "currying" or "partial function application", that is not directly supported in Rust. Writing a lambda is actually the idiomatic way to do that. Commented Dec 16, 2018 at 21:59
  • 3
    Imagine a function fn foo(a: i32, b: i32) {...}, then you call it fn main() { foo(4); }, with your proposal, this code would be legal but do nothing. Writing a lambda such as |x| foo(4, x) when it is really needed is far more reasonable, IMHO. Also you can as easily write |x| foo(x, 4) or any other variation with any number of arguments without compromising the rest of the language. Commented Dec 16, 2018 at 22:38
  • 2
    Sure you can write a macro that takes those arguments and expands to a lambda, but then, what is the advantage of apply!(has_repeated_letter, 3) over |x| has_repeated_letter(3, x)? Commented Dec 16, 2018 at 23:05
  • 2
    Could you not change the has_repeated_letter to return a closure like this Commented Dec 16, 2018 at 23:52
  • 1
    For your next question, please consider reducing the amount of fluff. Your question basically boils down to "I can do this [one argument example], how can I do this [two argument example]" The amount of background (the Advent of Code is basically completely irrelevant to the question) will make this question much harder for other people to read it to know if it's relevant to them when they have a potential duplicate. Commented Dec 17, 2018 at 1:32

1 Answer 1

4

No, you cannot pass a function with multiple arguments as an implicit closure.

In certain cases, you can choose to use currying to reduce the arity of a function. For example, here we reduce the add function from 2 arguments to one:

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

However, I agree with the comments that this isn't a benefit:

  1. It's not any less typing:

    a.map(curry(add, 2));
    a.map(|v| add(v, 2));
    
  2. The curry function is extremely limited: it chooses to use FnOnce, but Fn and FnMut also have use cases. It only applies to a function with two arguments.

However, I have used this higher-order function trick in other projects, where the amount of code that is added is much greater.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.