Simple partial application

Before further discussion if and how it would be useful… is it even possible?

Say, the proposal is desugaring f(_, y, _) to |x, z| f(x, y, z) and _.foo() to |x| x.foo(). Would that even work, given that one has to sprinkle a fair amount of *s and &s into closures?

1 Like

Some of these partial application syntax proposals are approaching https://www.boost.org/libs/lambda, which added lambdas to C++ as a library. For example, one could do _1 * 2 for what rust calls |x| x * 2. I suspect nightly Rust could do something similar today. (Nightly to be able to implement the Fn* traits.)

The biggest problem that has is figuring out the extent of the lambda:

  • How does one decide if foo().bar(_) is |x| foo().bar(x) or match foo() { t => |x| t.bar(x) }? (Not unlike the postfix macro discussions.)
  • If one has foo(_, _), is that |x, y| foo(x, y) or |x| |y| foo(x, y), or something else? Can one do the other?

So I’m not sure how much value there is there over |x|, which just isn’t that long.

(That said, currying can certainly be useful, so part of me likes the idea of being able to declare things as fn foo(x: i32)(y: i32) -> i32 so that foo(2): Fn(i32)->i32. But then it’d always be foo(1)(2), never foo(1, 2).)

6 Likes

It's not as obvious as you suggest. We use "_" as placeholder in a plethora of places today already:

let x : &'_ _ = &foo(|_| 42);

match (1, 2) {
  (_, _) => { println!("Hello! x = {}", x); }
}

Playground Link

Spotting partial application reliably would be difficult: Each "_" could indicate partial application or it could be just some other placeholder. OTOH the closure syntax that we have today can be spotted easily because its notation makes it obvious. IMO it's also short enough. I never felt that it was too verbose.

Edit: Another notable difference is that all uses of "_" today have something in common: The "_" serves as placeholder for something that is of no interest. Using it as placeholder in partial application would be something else entirely because it would represent a parameter.

4 Likes

Another notable difference is that all uses of “” today have something in common: The “” serves as placeholder for something that is of no interest. Using it as placeholder in partial application would be something else entirely because it would represent a parameter.

I think _ is the wrong token for this too. I'd prefer something like $, as it stands out and there is prior art for using $ for unnamed parameters.

I didn't want to mention it until there is some consensus if something like that is even desirable.

A social solution to this problem would be to standardize junk names for usage in closures. If everyone uses the same one character names it becomes easier to ignore them. Looking at code in the wild, we’re already halfway there with x, y and z.

1 Like

Slightly off-topic, but are you, per-chance, thinking of Iterator::cloned()?

4 Likes

It's not about length; it is about forcing me to come up with descriptive identifiers for the lambda binders just for the parameter passing, which is irrelevant to the algorithm described in the code. I could just use one letter identifiers for the binders, -- and I will eventually do that if I have to write a lot of lambdas... -- but one letter identifiers makes me feel rather bad, since it adds no useful information for the reader.

PS: my reddit nick is etareduce so that should tell you how I feel about these type of lambdas...

Some not-technically-prior art: Javascript is thinking of using ? for this. Personally, that’s a syntax I would not mind at all in JS or Rust. Though I don’t know Rust’s grammar well enough to tell if it’d be unambiguous. And Rust’s ? is way more popular than JS’s ?: ternary operator.

These problems are partially sidestepped by my proposal to require || before a partial application: in this syntax, it is clear that || foo().bar(_) becomes |x| foo().bar(x). However, it's not clear what || || foo(_, _) should do; if I had to guess, I'd say || |x, y| foo(x, y).

Looking at prior art, Scala jumped this ship a while ago, since it uses the "wunderbar" for both purposes:

// define mutable foo to have the default value for `Int`
var foo: Int = _ 

// don't bind parts of a match
(foo, bar) match {
    case (0, _) => println("zero!")
    case _ => println("something else")
}

// erased type parameter, equivalent to Java's Foo<?>
// (NOT please-infer-the-paramter-for-me, unlike Rust)
def foo: Foo[_] = .. 

// equivalent to xs.map { x, y => x + y }, the syntax we're 
// discussing today
xs.map(_ + _) 

I tend to read _ as "I want a variable here but I don't want to give it a name so I'll let the compiler make one for me". This is a valid interpretation of things like let _ = ..;, only that you can't pronounce that "variable", so it can be elided.

This is just my personal taste, but the $ is kind of ugly.

I feel like ? is just asking for readability trouble: imagine something like iter.map(?.foo()?.bar()), or worse still, iter.map(-?)! I think a lot of us are trained to think Result-propagation when we see ? and it'd be pretty jarring!

Swatches for the above syntaces (though I think we're just arguing about paint colors at this point).

iter.map(foo(1, $, $));
iter.map(-$);
iter.map($ + $);
iter.map($.foo($));

iter.map(foo(1, ?, ?));
iter.map(-?);
iter.map(? + ?);
iter.map(?.foo(?));

// for comparison, my proposal:
iter.map(|| foo(1, _, _));
iter.map(|| -_);
iter.map(|| _ + _);
iter.map(|| _.foo(_));
1 Like

I want to retain the ability to use _ for inference; right now it is only used in type context for that, but foo(_, 3) could be value inference in a dependently typed setting.

1 Like

Get a better font then! :stuck_out_tongue: Browse Fonts - Google Fonts

But honestly, I use Fira Mono (or rather, Fira Code), and I like the glyph in that font. Source Code Pro also has a good . I've got a user stylesheet in Firefox such that I haven't seen Consolas code for over a year until taking the below screenshot, and I agree that the Consolas is bad looking.

Font Preview
  • Fira Code
    image
  • Source Code Pro
    image
  • Consolas
    image

The only practical application I can imagine is things like foo([x; _]), which I don't find terribly compelling. Can you elaborate on what you envision this being useful for? I'm not saying this is a bad idea, but I don't think it's worth applying stop energy over a feature that I can't imagine there being an RFC for any time soon (const generics are not dependent types iirc).

$ is one of the few ASCII characters used in Rust that is absent from many non-US keyboards. Have pity on your colleagues outside the States and choose something other than the currency character of a limited set of countries.

To understand the problem, what if the chosen symbol were , which is not on most US keyboards?

Here’s an appropriate listing of candidate currency symbols. A number of them seem attractive.

2 Likes

You must go Lou Ferigno with Perl then? :slight_smile:

I don't care about the long-term evolution and survival of Perl. I do care about Rust; it offers a way out of the cyber-security morass inherent in other current programming languages.

I kind of hate closure noise. Even very simple examples like .filter(|&x|second(x)(i))

I get a bit confused because I have to make sure the precedence is correct

I prefer .filter(apply(second, i)) which is point-free, even if I have to write my own apply function which only does move |a| f(a)(b)

But that probably reduces again to a semantic ambiguity of the form “are second and i non-declared variables (and thus should yield compile errors), or are they pointfree notation?”.

How can that be dealt with without turning Rust into some language with rules as complex as C++ or Scala? Personally I’d prefer not to have to spend a few minutes reasoning and disambiguating each time I see something like this.

Of course there has been a suggested solution: _ or $ or something of the sort, but my argument against that is that it seems to move even further in the direction of needing to look at the definition to figure out what an expression using this is actually doing.

1 Like

Imo this thread has went a little bit of the rails.

Can we take a step back and recap? What is the actual problem this is a solution to?

Imo there are these two issues:

  • Argument names in closures have little meaning on their own, so they are hard to name and potentially distracting.
  • Closures are verbose and it would be nice to safe a few keystrokes. This issue may be less superficial than it seems, since closures are typically used inline. This also may make or break a potential pipe operator (a |> f(b, _) is practical, a |> |x| f(b, x) will get old quick)

These two issues are related, but distinct. I could imagine that the first issue could be handled with either a convention (everyone uses x or something for this) or just a symbol for “has to be named, but the name has no meaning” that is allowed as an argument name ( |$| $.do_stuff() or something like that) The second issue would require a solution that introduces new syntax and it would be really nice if it would work with operators (list_of_strings.filter(_.len() < 5) for example).

I hypothesize that for both issues it’d be enough to just support single argument closures.

1 Like

I have a macro crate for partial application of functions: repo | docs . It’s also on crates.io

partial!(some_fn arg0, _, arg2, _) is translated to |x1, x3| some_fn(arg0, x1, arg2, x3). Optionally, function call parentheses are allowed:
partial!(some_fn(arg0, _, arg2, _)) == partial!(some_fn arg0, _, arg2, _)

I may be able to support self.foo(_).bar(_, 1) syntax. Parsing shouldn’t be an issue and the value inside the closure could hopefully be recursively built up as {{{self}.foo(x0)}.bar(x1, 1)}.

7 Likes

That would also look quite neat as:

some_fn.partial!(arg0, _, arg2, _)
1 Like