Why is functional Currying right associative in rust?

As you can see, the right expression is returned and used as the input for the left closure. This process is called querying. Why is it right-associative in Rust?

fn calculate_translation(border: f32, current_number: f32) -> f32 {
    (|half_border| {
        current_number
            + rand::rng().random_range(match current_number {
                n if n >= half_border => -40.0..0.0,
                n if n <= -half_border => 0.0..40.0,
                _ => -40.0..40.0,
            })
    })(border / 2.0)
}

I don't understand the question here.
There is exactly one closure within the function calculate_translation(...), namely

|half_border| {
        current_number
            + rand::rng().random_range(match current_number {
                n if n >= half_border => -40.0..0.0,
                n if n <= -half_border => 0.0..40.0,
                _ => -40.0..40.0,
            })
    }

and it is directly called with the argument border / 2.0.
What would you expect otherwise?

1 Like

It's not clear for me, either. What is right-associative and relative to what?

I would expect, that it works like this:

fn calculate_translation(border: f32, current_number: f32) -> f32 {
    (border / 2.0)(|half_border| {
        current_number
            + rand::rng().random_range(match current_number {
                n if n >= half_border => -40.0..0.0,
                n if n <= -half_border => 0.0..40.0,
                _ => -40.0..40.0,
            })
    })
}

This looks far more logical because the outcome of the expression on the left is used in the expression that follows. To be fair, this won't cause any problems with understanding in my short example. It could be problematic in something like this: (|x| x²)(|x| x/2)(25 × 2). It would be difficult to tell whether the left or middle expression is called first.

Note that (|x| y)(z) is not a special piece of syntax in Rust. It's just a function call operator, f(z), with the function being a closure (lambda expression), |x| y. If function call operators worked in the opposite order, then you'd have to write all your function calls in reverse the same way too, like ()rand::rng; I doubt you want that.

The normal Rust way to get a variable binding like this would be:

fn calculate_translation(border: f32, current_number: f32) -> f32 {
    let half_border = border / 2.0;
    current_number + ...
}

I'm curious where you learned the term “functional querying”. I haven't heard it before and I'd like to know what it is supposed to be about. (From the JavaScript community I know of calling (|x| y)(z) an “IIFE” (immediately invoked function expression”.)

10 Likes

This would be trying to use a number as a function, which makes no sense.

The syntax for a function call is expression ( expression ) so there is only one valid parse tree here: ((|x| x²)(|x| x/2))(25 × 2). There's no associativity involved.

3 Likes

Function application is left-associative. For some reason the documentation doesn't state it, but it is left-associative, hence this your example of (|x| x*x)(|x| x/2)(25 * 2) is equivalent to ((|x| x*x)(|x| x/2))(25 * 2). And therefore it won't compile because functions can't be multiplied.

But you aren't really talking about associativity, you're talking about the order of execution. This is also left-to-right here. Your example is equivalent to:

    let a = |half_border| {
        current_number
            + rand::rng().random_range(match current_number {
                n if n >= half_border => -40.0..0.0,
                n if n <= -half_border => 0.0..40.0,
                _ => -40.0..40.0,
            };
    let b = border / 2.0;
    a(b)

The value of a, which is a closure, is computed first.
The value of b is computed second.

Finally, there is the function application, meaning the value of b is passed as an argument to the closure a, and the body of the closure is executed with that argument. The body of a function isn't executed the moment the function is defined. It is executed when the function is called, which can happen multiple times, and obviously after the parameters are passed to the function.

4 Likes

It's called querying ... by whom, exactly? [1]

Doesn't it mean that the inner call is rather illogical too? As in:

rand::rng().random_range(match current_number {
    n if n >= half_border => -40.0..0.0,
    n if n <= -half_border => 0.0..40.0,
    _ => -40.0..40.0,
})

the result of the match'ing against the current_number is used in the random_range call - thus:

match current_number {
    n if n >= half_border => -40.0..0.0,
    n if n <= -half_border => 0.0..40.0,
    _ => -40.0..40.0,
}(rand::rng().random_range)

must be the "far more logical" way to go, as well?


  1. For the love of all that is holy, I do hope that this "functional querying" thing is not just another "hallucination" of an AI/LLM prompted to come up with a (pseudo-formal) explanation as to the way the Rust's compiler's supposed to work under the hood. ↩︎

1 Like

Oh, you're right. It's actually called Currying.

Function application in Rust always looks like a function term, followed by an argument-list term. It doesn't matter what those terms are, syntactically.

foo(1, 2, 3); // function term foo, argument-list term (1, 2, 3)
self.foo(1, 2, 3); // function term self.foo, argument-list term (1, 2, 3)
(|a, b, c| ())(1, 2, 3); // function term (|a, b, c| ()), argument-list term (1, 2, 3)