Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Currying RFC #191

Closed
wants to merge 1 commit into from

Conversation

Projects
None yet
@iopq
Copy link
Contributor

iopq commented Aug 5, 2014

Automatic currying of all functions and closures

You get something like partial application when all functions are curried.
Thus, you can write `fn add(a: int, b: int) -> int { a + b }` and call it with `add(1)`
and get `|b: int| -> (b + 1)` back. This means you can get closures easily without
having heavy closure return types. This pattern allows you to code without having all

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

There's no need for 'heavy closure return types', e.g. just writing |b| add(1, b) works.

This comment has been minimized.

@iopq

iopq Aug 5, 2014

Author Contributor

if I return |b| add(1, b) from my function I will have to declare the function's return type to be -> |int| -> int

if add is implemented that its return type is Vec<Rc<Cell<int>>> then the return type of the function when curried is |Vec<Rc<Cell<int>>>| -> Vec<Rc<Cell<int>>>

This comment has been minimized.

@huonw

huonw Aug 6, 2014

Member

I think there's basically two reasons one would explicitly return a closure:

  1. to make currying easier
  2. to be able to cache some work that can be reused repeatedly later

The first is (slightly verbosely) replaced by writing |b| add(1, b) at the call site, and automatic currying is not really a replacement for the second.

To replace 2, the compiler would have to do some form of partial evaluation/partial application, which I imagine would be quite difficult to do in a reliable manner in Rust, i.e. if it actually mattered for performance a human would probably do the partial application transformation manually anyway.

# Detailed design

A function called with no arguments remaining in its signature is called immediately.
A function called with some arguments remaining is turned into a closure.

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

This isn't very detailed. It should at least vaguely the discuss the mechanics of 'turned into a closure', I would imagine that unboxed closures with by-value captures come into play and are worth mentioning, as would exhibiting some code transformations into explicit closure form, e.g. foo(a, b)(c, d); becoming (|x, y| foo(a, b, x, y))(c, d).

Also, what about efficiently handling static vs. dynamic function calls? i.e. let f: Box<FnTrait> = if ... { some_2_arg_closure } else { some_other_2_arg_closure }; let g = f(1); g(2) would presumably need to capture f into the g closure somehow, but would it capture by-value or by-reference? (Sometimes it will need to be by-value, e.g. when currying a FnOnce closure, sometimes it will need to be by-reference, since it is possible to continuing using f later.)

But, then, it would be inefficient (at compile time) to actually capture the function into a closure for static function calls (like calling add(1, 2) above); this would bloat our IR significantly and slow LLVM to a creep (this is an implementation detail but it'd be nice to mention it, since compile times are very user-visible).

Lastly, what about more interesting curries:

  • methods
  • overloaded call operator (this definitely needs discussing since it is how unboxed closures work)

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

Also, what does foo() do? For uniformity, it 'should' just return a closure around foo, since, if foo takes n args, it's passing 0 out of n, and thus should return a closure taking n args itself. The worst part is when n == 0.

This comment has been minimized.

@iopq

iopq Aug 5, 2014

Author Contributor

No, because when you reach the 0 argument case it just executes. 0 is the base case, you execute until you reach it. As I understand it, in Haskell getChar doesn't curry just because it has no arguments. It just executes and reads from standard in.

If I write main = getChar it won't ask for more arguments. It will just execute getChar.

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

getChar is not a zero-argument function, (from the outside) it's just a value representing an IO action, that can be executed by being sequenced in main.

Anyway, as I said above 0 is not "logically" the base case, especially not for a closure taking at least one argument, e.g. let f = |a| { ... }; f().

A function called with some arguments remaining is turned into a closure.
`a -> b -> c` is right-associative, equivalent to `a -> (b -> c)`

# Drawbacks

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

Another drawback is likely making error messages slightly more confusing, e.g. missing an argument now is:

fn add(x: int, y: int) -> int { x + y } 

fn main() {
    let z = add(1) * 2; // error: this function takes 2 parameters but 1 parameter was supplied
    println!("{}", z);
}

but under this RFC it would (naively) become something like "type |int| -> int does not implement Mul". (This should either be a drawback or a plan to avoid these problems should be mentioned above. Investigating how e.g. Haskell handles might be a good idea.)

This comment has been minimized.

@dobkeratops

dobkeratops Aug 5, 2014

Another drawback is likely making error messages slightly more confusing,

^ could the context be used to guess a bit: if you're passing an expression to a function argument, assume the user wants to curry. Anywhere else, give a warning about missing parameters first "add takes 2 parameters; curried to |int|->int"

This comment has been minimized.

@huonw

huonw Aug 5, 2014

Member

Yes, we could infer/guess; but it needs to be written down, because it's very important.

@SimonSapin

This comment has been minimized.

Copy link
Contributor

SimonSapin commented Aug 5, 2014

-1. Explicit currying would be nice (either through a macro or custom syntax), but I think that implicit currying is too much magic.

@dobkeratops

This comment has been minimized.

Copy link

dobkeratops commented Aug 5, 2014

how about allowing arity-based overloads ,enabling the community to implement currying or default parameter macros , and see how it fits in the language in widespread use

@killercup

This comment has been minimized.

Copy link
Member

killercup commented Aug 5, 2014

I too would like to see currying done explicitly (let square = power(_, 2)).

First, because explicit is better than implicit (and simple is better than complex), and second, because then it would be almost orthogonal to parameters being named and/or optional.

In my ideal world, it would be possible to say let completed = reduce(_, callback = |acc, x| { acc + 1 }, acc = 0), where completed will be called with a _ that is required to implement reduce (and the reduce call above will actually be akin to Uniform Function Call Syntax, i.e. calling _'s reduce method: completed(user_list) == user_list.reduce(callback, acc)).

@lilyball

This comment has been minimized.

Copy link
Contributor

lilyball commented Aug 5, 2014

-1 as well. I like the idea of explicit currying though.

@liigo

This comment has been minimized.

Copy link
Contributor

liigo commented Aug 6, 2014

prefer explicit
Sent using CloudMagic
On 周三, 8月 06, 2014 at 5:33 上午, Kevin Ballard notifications@github.com wrote:-1 as well. I like the idea of explicit currying though.

—Reply to this email directly or view it on GitHub.

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Aug 6, 2014

I'm obviously biased, but I'll just point out that I think the way this RFC handles call-sites would conflict with my proposal for auto-tupling at call-sites: http://discuss.rust-lang.org/t/pre-rfc-auto-tupling-at-call-sites/175

(although maybe a sufficiently clever programmer could use the auto-tupling proposal to implement calls to a curried function.)

@dobkeratops

This comment has been minimized.

Copy link

dobkeratops commented Aug 6, 2014

suggestion for the OP: maybe separate the discussion of keyword arguments and defaults - mention in 'alternatives' or 'drawbacks' the option of currying being mutually exclusive with 'trailing defaults', also the suggestion of explicit sugar to disambiguate either.

also r.e. 'functions vs methods' - you could suggest only currying free-functions initially, to avoid issues about what people expect 'self' should do. One could always wrap a free function around some method-dispatch

this RFC handles call-sites would conflict with my proposal for auto-tupling at call-sites:

could it still co-exist - this auto-tippling is what to do when you supply more arguments, whereas currying (and even defaults) deal with passing fewer arguments. You could simply say that the variadic arguments are never curried.
IMO currying,defaults, and variadic/auto-tupling would all suit different situations.

@lilyball

This comment has been minimized.

Copy link
Contributor

lilyball commented Aug 6, 2014

FWIW, it should be possible to write a curry!(power(_, 2)) syntax extension. Heck, you could probably even write a bind!(power($2, $1)) extension that would let you reorder arguments (similar to C++'s std::bind), although using nonterminal tokens like that (if it even parses them with digits, I'm not sure) might possibly have issues if you're embedding a macro call (though hopefully not, as you'd replace all of them before the embedded macro is evaluated). Or you could do something like bind!(power(_2, _1)) which is a bit more reminiscent of the placeholders used by C++.

@bgamari

This comment has been minimized.

Copy link

bgamari commented Aug 6, 2014

@kballard in my opinion you the succinctness of Scala's explicit currying is its primary draw. Introducing a curried call with a macro negates most of this advantage.

@dobkeratops

This comment has been minimized.

Copy link

dobkeratops commented Aug 7, 2014

in my opinion you the succinctness of Scala's explicit currying is its primary draw. Introducing a curried call with a macro negates most of this advantage.

+1. as soon as you wrap it in a macro.. you might aswell just write a closure.. its just a prefix |x,y| vs bind!(...) the closure might even be fewer characters already

@lilyball

This comment has been minimized.

Copy link
Contributor

lilyball commented Aug 7, 2014

The macro approach has the benefit of letting you reorder arguments. I like the power(_, 2) syntax though, which doesn't support reordering but is also concise.

@Drup

This comment has been minimized.

Copy link

Drup commented Aug 8, 2014

The nice thing about the syntax power(_, 2) is that it's compatible with both labeled and optional arguments (It was discussed a bit here).

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Aug 14, 2014

Thank you for the suggestion. During the triage meeting today, we decided to close this RFC. As @huonw points out, there are numerous issues that would have to be addressed to flesh out the proposal, but even more than that I think there is a general feeling that automatic currying is not something we plan to add to Rust.

withoutboats pushed a commit to withoutboats/rfcs that referenced this pull request Jan 15, 2017

Rewrite the stream::channel implementation
The previous implementation suffered a fatal flaw (rust-lang#191) where to make progress
one half was required to block literally waiting for the other. The whole point
of all the lock free shenanigans was to avoid that, so it clearly didn't solve
its intended problem!

This commit completely rewrites `stream::channel` away from this pesky `Slot`
abstraction in a way that is geared towards avoiding this race we discovered.
Lots more details can be found in the implementation itself.

Closes rust-lang#191

@Centril Centril added the T-lang label Mar 18, 2018

wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019

Merge pull request rust-lang#191 from emberjs/deprecate-component-args
Deprecate component lifecycle hook arguments.

wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.