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

Keyword arguments #805

Closed
wants to merge 1 commit into from

Conversation

Projects
None yet
@iopq
Copy link
Contributor

iopq commented Feb 3, 2015

Adds keyword arguments as a separate feature in a backwards-compatible way. The current functions are unaffected - only arguments declared as keyword arguments can be called this way. Keyword arguments can be mixed with normal arguments. Adds overloading in the general sense without breaking type inference allowing a later addition of sugar for optional or default arguments if this is desired.

@iopq iopq force-pushed the iopq:keyword-arguments branch from c630e04 to 2e41311 Feb 3, 2015

```rust
fn slice(&self, from => begin: usize, to => end: usize) -> &'a str
fn slice(&self, from => begin: usize) -> &'a str
fn slice(&self, to => end: usize) -> &'a str

This comment has been minimized.

@hauleth

hauleth Feb 3, 2015

Nope. I would vote against this. Why introduce another keyword when we have one? We could use to => usize instead as signature that this is keyword argument or default arguments, so this will be:

fn slice(&self, begin: usize = 0, end: usize = /* something, maybe `self.length()` */) -> &'a str

Anyway syntax need reconsideration.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

@hauleth: I think it's a little disingenuous to pick on the specific example. I can go one better: why have a slice method at all when we now have overloadable indexing and native range syntax? I agree a better example could be chosen, but that's not really the point of this.

This comment has been minimized.

@hauleth

hauleth Feb 3, 2015

@DanielKeep I could use any example (but this one was poor anyway). Although the meritum was here. Why introduce new keyword when we have one already? We should reuse what we have instead adding some creepy syntax (like Ruby have done).

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

Default/optional arguments are compatible with this design. They're really sugar for a form of overloading, so they can be added later if keyword arguments are added. Or maybe people will simulate them with macros and it won't be necessary.

The point is that this is outside of the scope of just adding keywords. You can argue merits and syntax for default arguments, but that's not the same concern as the keyword arguments themselves.

Fn(&self, from => begin: usize, to => end: usize) -> &'a str
//which desugars to
Fn<({from: usize, to: usize}, &Self), &'a str>
```

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

What about function types? The slice method would have a subtype of for<'a> fn(&'a str, usize, usize) -> &'a str which is not the same as the Fn trait.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

What is the desugaring if there are no keyword arguments?

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

Same as now? I thought this could be added to affect only the functions for which keyword arguments actually exist

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

But then, how do you tell the difference between a function that has keyword arguments and a function that has a struct as its first/last argument?

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

I imagine that this would be equivalent, and you could call a function that does this now with the keyword syntax in the future if this is implemented. Although I'm not sure if my vision for keywords being a part of the function's signature is compatible with that.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

These are the sort of details I think need to be addressed in an RFC. You've said that:

fn slice(&self, from => begin: usize, to => end: usize) -> &str

Desugars to

for<'a> Fn<({from: usize, to: usize}, &'a str), &'a str>

But what about

fn as_slice(&self) -> &str

What does that desugar to?

for<'a> Fn<(&'a str,), &'a str>
for<'a> Fn<((), &'a str), &'a str>
for<'a> Fn<({}, &'a str), &'a str>

Also, given that the Fn* traits aren't even fundamental types, I think you have to define what happens to fn types. And how that interacts with FFI.

This comment has been minimized.

@iopq

iopq Feb 4, 2015

Author Contributor

I just addressed it here. fn as_slice(&self) -> &str desugars to the same thing it always desugared to, for<'a> Fn<(&'a str,), &'a str>, nothing has changed because it doesn't have keyword arguments so this proposal doesn't affect current code.

window.addNewControl({ xPosition: 20, yPosition: 50, width: 100, height: 5,
drawingNow: true });
```
If this wasn't a convenient pattern, nobody would bother to do it.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

If this is good enough for JavaScript, why is it not good enough for Rust? What about reformulating it as fn add(&mut self, Control) where Control is a structure with the given fields. That would also allow for users to omit fields as "optional" arguments using the ..Default::default() syntax; obviously a convenient default wrapper function would help there, but the point still stands.

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

Because you have to declare a struct manually, while in JavaScript everything is ducktyped. It is considerably more difficult to do the same thing in Rust. Also, by allowing keywords you get overloading. You would have to make some kind of Traits to achieve the same thing in Rust.

This comment has been minimized.

@hauleth

hauleth Feb 3, 2015

Overloading in Rust is IMHO absolutely unneded. We should use meaningful function names instead of that. Keyword arguments are different way of using object factory and IMHO there is no need for them as we can easily simulate them by using struct like @DanielKeep said. Only (and controversial) income from keyword arguments would be that we can reorder arguments in any way, i.e.

fn from_cartesian(x: i64, y: i64) -> Self { … }

Point::from_cartesian(y => 2, x => 4)

And that will save us from checking documentation for arguments order. I would oppose this if it will be tool to add function overloading as I consider it harmful.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

I'm bringing this up because it's a fairly obvious hole in your argument that you simply don't address. I mean, you could take the argument object pattern in JS as an indication that keyword arguments are desirable or that just passing an aggregate structure is a perfectly viable alternative. You should address both sides honestly and fairly, which will strengthen the RFC.

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

I would argue that the function's keywords are part of its name so you'd have a function from_cartesian(x=>,y=>) that would correspond to a Smalltalk message send like fromCartesianWithY: x: except you still have a function name so you don't have to smoosh that into the first argument. So you can't "overload" with another from_cartesian(x=>, y=>) that accepts f64 or any other kind of silly overloading other than the function name and its keyword arguments.

This comment has been minimized.

@steveklabnik

steveklabnik Feb 4, 2015

Member

If this is good enough for JavaScript, why is it not good enough for Rust?

I don't have any position on this RFC yet, but there are many things that are good enough for other languages that aren't for Rust. Garbage Colletion being a primary example.

This comment has been minimized.

@nikomatsakis

nikomatsakis Feb 5, 2015

Contributor

Note that because Rust requires you to name the type of the struct in a struct literal, the syntactic overhead is much higher than in JS.

let position = Position(20, 50);
let dimensions = Dimension(100, 50);
window.addNewControlDrawingNow(title, position, dimensions);
```

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

This isn't really an alternative; both because it's something you can do right now and because you have to remember the order. I think this falls under the traditional "alternative" of "don't change anything".

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

Yes, that's the "do nothing" alternative.

It is still up to the programmer to actually name those variables well, instead of the API specifying what the keywords should be.

If keyword arguments themselves are not implemented, then there's also the issue of overloading to enable better API design.
Another proposal would be to assign keywords to all current Rust code with their local names.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

This really threw me for a loop: "assign keywords". This implies that you want to make all existing argument identifiers reserved keywords in the language or something. Unfortunate overloading of the term, I suppose.

Maybe reword to something like: "allow any argument to optionally be used as a keyword argument".

Even though it has a different name, being able to call keyword arguments without keywords breaks overloading.

With mandatory keywords it is always known what type a certain keyword is.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

You haven't included the alternative that you demonstrated being used by JavaScript: passing a struct or enum.

This comment has been minimized.

@iopq

iopq Feb 3, 2015

Author Contributor

This is not an "alternative" since my proposal is sugar for this very thing. What you're really passing is a struct of arguments that have keywords, but you don't have to actually declare it.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

It's something you could do instead to achieve the same effect. An RFC is about making a solid technical and practical argument for why the change should be made. If you ignore practical, viable alternatives, it just gives people ammunition to blow holes in your argument with. :)

It would be better for the RFC to acknowledge the alternative, then address why you feel it's not sufficient.

In this particular case, yes, the sugar is going to be shorter, but I don't see it as being a huge imposition to do it by hand. The RFC should convince me otherwise, show me the objective light!

This comment has been minimized.

@jorisgio

jorisgio Feb 3, 2015

What about using the same structure syntax as javascript as sugar ? Something like :

fn foo<T>({ self, len : usize, f : Fn(T) -> u32 }) -> u32

I don't see why it would be an issue. Indeed, structure literals require a struct name, and in this case you would just call the function like this, without struct name:

x.foo({f : |x| x +1, len  : 42us }).

Alternatively, if you want only reordering, and no overloading, you can still call the function with the default order :

x.foo(42, |x| x + 1)

I feel like this is a viable alternative, or am i missing something ?

This comment has been minimized.

@iopq

iopq Feb 4, 2015

Author Contributor

I think self should be a positional argument. The problem I have with this design is that you may put the struct containing keyword arguments in any position.

fn foo<T>(self, {len : usize, f : Fn(T) -> u32 }) -> u32 or is it
fn foo<T>({len : usize, f : Fn(T) -> u32 }, self) -> u32?

I would like some extra sugar so that there is only ONE struct with keyword arguments and it's in a predictable location.

Could it come first (before the self parameter) or does it have to come last?

Another possibility is a different syntax for keywords. Maybe it would be better to unify it with the struct syntax.
So something like `foo.slice(begin: 5, end: 10)` because it desugars to a keyword struct anyway.

This comment has been minimized.

@DanielKeep

DanielKeep Feb 3, 2015

Unlikely to be possible while leaving the door open for type ascription.

This comment has been minimized.

@comex

comex Feb 3, 2015

To repeat a claim I've made on discourse: syntactic consistency between two very similar operations - this and struct construction - is more important than type ascription.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Feb 3, 2015

First of all, overloading on arity and default arguments are important topics, but they aren't directly related to named arguments.
With regard to passing integral parameters safely, there are enough tools already - newtypes, struct literals (as mentioned in your Javascript example), builders.
The listed benefits are relatively small compared to the existing solutions, and the questionable practice of using functions with lots of arguments is promoted. The arising complexity - namespacing of the argument names, ABI implications - isn't mentioned at all.

Personally, I think it's one of those dangerous proposals which turns a language into a feature creep by adding more and more nonessential bells and whistles.

@BurntSushi

This comment has been minimized.

Copy link
Member

BurntSushi commented Feb 3, 2015

I am in violent agreement with @petrochenkov. I agree that there is an ergonomic benefit to keyword arguments as defined here, but I think the added complexity is not worth it.

@lifthrasiir

This comment has been minimized.

Copy link

lifthrasiir commented on active/0000-keyword-arguments.md in 2e41311 Feb 3, 2015

Type-level records, really? This alone is complex enough that the implementation cost cannot be easily justified. It should be also noted that this RFC does not describe the constructor syntax for these types; is it supposed to be built-in only? (I personally don't think so.)

@Nercury

This comment has been minimized.

Copy link

Nercury commented Feb 3, 2015

Maybe we could start simpler at first, for example, work with overloading on arity, then later (if it makes sense) adopt argument defaults, and only then (if it makes sense) work on named arguments. And I still wonder about argument order based on type.

I also think that languages like PHP or JavaScript are bad examples, and history of C# would be a better one, but that is just a personal opinion.

@hauleth

This comment has been minimized.

Copy link

hauleth commented Feb 3, 2015

I think (if the "keyword arguments" is so needed) the best solution is to allow just omiting structure name when creating and passing to the function, i.e.:

struct Foo {
  a: u32,
  b: u32
}

fn bar(foo: Foo) {}
fn baz(foo: &Foo) {}

bar(Foo {a: 10, b: 10});
// would be the same as
bar({a: 10, b: 10});

// and

baz(&Foo {a: 4, b: 2});
// equals
baz(&{a: 4, b: 2});
@andrew-d

This comment has been minimized.

Copy link

andrew-d commented Feb 3, 2015

I think (if the "keyword arguments" is so needed) the best solution is to allow just omiting structure name when creating and passing to the function

+1 from me - this is a reasonably small change, and has nearly the same ergonomics as "proper" keyword arguments.

@aliblong

This comment has been minimized.

Copy link

aliblong commented Feb 3, 2015

Maybe we could start simpler at first, for example, work with overloading on arity, then later (if it makes sense) adopt argument defaults, and only then (if it makes sense) work on named arguments. And I still wonder about argument order based on type.

This seems like a more reasonable course of action than just throwing it all into the language at once; whether it's worthwhile to add even arity overloading and/or arg defaults is debatable.

I think (if the "keyword arguments" is so needed) the best solution is to allow just omiting structure name when creating and passing to the function.

At first glance, this seems to go against the Rust philosophy of no implicit conversion.

@hauleth

This comment has been minimized.

Copy link

hauleth commented Feb 4, 2015

@aliblong I wouldn't say that this is implicit conversion. I would rather say that this is type detection based on argument type, like with integers.

@iopq

This comment has been minimized.

Copy link
Contributor Author

iopq commented Feb 4, 2015

Or you could start simpler at first and add keyword arguments without overloading. The difference between my proposal and hauleth's proposal is that my struct is anonymous and has extra sugar like => for binding function locals to the members of the struct. I also want to avoid ambiguity about WHERE the struct comes. It should be ONE struct with keyword arguments that always comes in the same position.

I don't care what the syntax is, but if the ergonomics makes it painful to use in any way nobody will bother in reality. If you always have to unpack a struct that seems like unnecessary boilerplate.

@erickt

This comment has been minimized.

Copy link

erickt commented Feb 4, 2015

I'm also not ready yet to take a side on this RFC, but I'd like two additions. First to the alternatives is the builder pattern that works incredibly well with movable types. Your example could instead be written as:

window.add_new_control()
  .title("Title")
  .x_position(20)
  .y_position(50)
  .width(100)
  .height(50)
  .drawing_now(true)
  .build()

It does require more boilerplate on the definition of a method though, but that could be addressed with some clever macros. Second, we used to have anonymous record types a few years ago. It would be nice to dig up the arguments for why we removed them in favor of named struct types, see if the argument to remove them applies to your RFC, and finally see if those arguments are still valid today.

@DanielKeep

This comment has been minimized.

Copy link

DanielKeep commented Feb 5, 2015

@erickt: I found rust-lang/rust#3089, which mentions that the primary reason for removing structural records was that they didn't play nice with coherence, meaning you couldn't ever implement anything on them.

@iopq

This comment has been minimized.

Copy link
Contributor Author

iopq commented Feb 5, 2015

But that's no different from tuples, and tuples are in the language. I don't understand the reasoning here.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 5, 2015

I'm interested in keyword arguments for the future, but I would rather wait to consider them until after 1.0.

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 5, 2015

postponing for post 1.0

@pnkfelix pnkfelix closed this Feb 5, 2015

@pnkfelix pnkfelix added the postponed label Feb 5, 2015

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 5, 2015

(there is already an issue for this topic in the postponed set, see #323.)

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.