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

Default arguments and keyword arguments #6973

Closed
tj opened this issue Jun 6, 2013 · 70 comments
Closed

Default arguments and keyword arguments #6973

tj opened this issue Jun 6, 2013 · 70 comments

Comments

@tj
Copy link

tj commented Jun 6, 2013

I didn't see any issues for keyword arguments, any plans for this in the future?


NOTE: The bug tracker is not the place to have a design discussion. Please direct all design discussion to the etherpad (https://pad.riseup.net/p/hvbg6dQQnEe7) or create a bikeshedding page on the wiki.

Etherpads:

@bstrie
Copy link
Contributor

bstrie commented Jun 11, 2013

There have been no plans for it, though nobody has yet voiced any opposition. I've been thinking about this issue for a long time, and it basically boils down to a request for default arguments. I suppose it would look something like:

fn foo(bar: int, qux=2: int, ham=0: uint) { ... }

Which could then be called as foo(1), foo(1, 3), foo(1, ham=4), foo(6, 7, 8), etc.

EDIT: Please see huonw's proposed syntax below, which looks much better and more uniform with the current declaration syntax.

@huonw
Copy link
Member

huonw commented Aug 5, 2013

Triage 2013-08-05: no progress (that I know of), although it'd still be neat; maybe the declaration syntax could look like

fn foo(bar: int, qux: int = 4, ham: Option<int> = None) { ... }

where the RHS is any constant expr (i.e. things that are valid in a static declaration).

@dobkeratops
Copy link

IMO these are really useful, even the C++ system of default arguments where you can only fall back to defaults for trailing unspecified arguemnts would be great... cuts down on the number of alternate named function variants whilst not being as complex as overloading .. being able to declare those in structs and enum variants aswell would be great.
Perhaps this simpler subset of the required behaviour could be implemented before there is consensus on how/if to call named keyword parameters ... it would get the internals ready ?

would it be viable/useful to implement as expressions ...in terms of earlier specified arguments & generic type parameters (eg.. ability to lookup a zero:: , or write things like slice(&self, start:uint,end:uint=self.len()) /* "foobarbaz".slice(6) == "baz" .. or create_window(parent,x,y,width:uint=parent.width()/4,height:uint=(width_161)/100) /_ default is golden-ratio shaped windows, and 1/4 screen width if you specify no size at all.. */ .... or does that just sound like a recipe for code bloat..

c++ style defaults would preclude haskell-like partial functoin-application, but i heard that was unlikely to go in?

@bstrie
Copy link
Contributor

bstrie commented Aug 9, 2013

Here's another idea for default arguments. Rather than allowing arguments to be completely elided, we allow arguments for which a default exists to be invoked with a _.

So given @huonw's lovely example of:

fn foo(bar: int, qux: int = 4, ham: Option<int> = None) { ... }

...we could invoke this as foo(1, 2, None), foo(1, _, Some(1)), foo(1, _, _), etc.

However, this idea wouldn't really provide any benefits to adopting keyword arguments, given that it's still strictly positional.

@bstrie
Copy link
Contributor

bstrie commented Aug 9, 2013

Changing title to "Default arguments and keyword arguments".

@dobkeratops
Copy link

not implemented, but i tried adding to the ast & parser; https://github.com/dobkeratops/rust/compare/default_args, is this the right way to go about it ? (Option@expr .. it'll need contstraints on what the expr can be ? )
dipping my toes into the water to experiment with this feature.
i'd be keen on this to increase the subset of C++ api's that could be translated, and they're definitely something i miss from C++, and the named keyword feature would be awesome beyond what c++ does.
It seemed simple and logical to go with the second suggested declaration syntax

@huonw
Copy link
Member

huonw commented Aug 11, 2013

@dobkeratops that looks good (although the type Default = Option<@expr> is probably unnecessary).

The constraints on the expr go in rustc::middle::check_const; you'll have to add a function like check_fn that checks each arg. (Also, presumably there should be a check that default args only appear at the end; and I guess there'll need to be something in the type-checker rustc::middle::typeck, and in trans too.)

@dobkeratops
Copy link

Ok the field is named so thats un-necasery annotation.
Thanks for the pointers on where to look, I was looking in rustc::middle::typeck::.. mod.rs (check_fn something ).

in the name of 'continuous integration' , is it worth me submitting the trivial groundwork as a pr, if I fizzle out on this maybe someone who knows the codebase better could do the other parts a lot quicker another time .
I'm always paranoid about diverging changes

Its only destructive if you definitely decide against them ?

Someone suggested this sort of thing can go in with a -Z option (..Session::debug_opts...but i didn't find these swere currently propogated everywhere (eg in the parser).

I can see you might object to the compiler parsing something it doesn't yet use, i could change it from a warning to an error perhaps...

@bstrie
Copy link
Contributor

bstrie commented Aug 11, 2013

@dobkeratops Note that no official developer has commented on this yet, so it's very unlikely that any PR would be accepted. There's a lot of dicussion to be had here regarding 1) whether we want default arguments, 2) if so, what the syntax should be, and 3) whether we want keyword arguments (because the semantics that we choose for default arguments could preclude this).

@dobkeratops
Copy link

Ok well i'd guess they'd be a very low priority for a long time yet,since they dont block anything (they're certainly not top of my own wish-list).
Just thought it might be low hanging fruit, and another familiarity for C++ people ..
I suppose macros can sometimes do a similar job (and more).. and I can get into better naming habits for common/uncommon variations

@Seldaek
Copy link
Contributor

Seldaek commented Aug 12, 2013

I can see that named arguments aren't critical, but IMO optional args kind of are. Because in some places I think I saw a few methods with similar signatures and similar names just to offer easier interfaces when a given arg is not needed. It would suck if we end up with legacy functions like that just because a feature was missing.

@graydon
Copy link
Contributor

graydon commented Aug 12, 2013

There are a lot of subtle parts in here. Anything default arguments are an implicit form of overloading, so (I'm guessing) likely get integrated with the trait resolution algorithm and/or type inference. It might not be super difficult but it's worth careful design, and at this point I'm not sure we have the time budget for it. As a language feature I imagine it's backward compatible; as an api feature it would probably inform api design some, so represent b-c risk.

@bstrie
Copy link
Contributor

bstrie commented Aug 12, 2013

@Seldaek I agree that if we want default arguments, then it's important to implement them before we commit to stdlib backwards-compatibility. However, I know of at least one language (Go) that philosophically rejects default arguments in favor of the functions-with-slightly-different-names-and-signatures approach.

However, unlike Rust, Go has a few features that make the absence of default arguments less painful. For one, they have variadic functions.[1] Secondly, you are allowed to omit fields when creating structs, which means that it's easy to pass a configuration struct to a function (the omitted fields appear to be set to compiler-specified(?) default values, rather than being something a user can customize).[2]

For the former, we might be able to get away with macro-hackery to accomplish the same result (though at laborious implementation cost). Indeed, the forthcoming replacement for fmt!() does this:

ifmt!("{foo} {1} {bar} {0}", 0, 1, foo=2, bar=3)

This is code that works today.

As for the latter, the best analogue that we have would be to use functional struct update as per the following:

struct Foo { x: int, y: int, z: int }

impl Foo {
    fn default() -> Foo {
        Foo{x: 0, y: 0, z: 0}
    }
}

fn bar(f: Foo) {
    printf!(f);
}

fn main() {
    bar(Foo{y: 5, ..Foo::default()});
}

I've seen this method offhandedly suggested in the past as a workaround for the lack of default arguments, but it's quite ungainly in practice (compare bar(Foo{y: 5, ..Foo::default()}) to bar(y=5), or my more conservative proposal of bar(_, 5, _)), and of course it forces all callers to create a struct even if they do want to pass all the arguments.

To reiterate, as one who comes from Python and Javascript I'd obviously welcome default arguments. However, I do somewhat empathize with Go's philosophy of making APIs explicit (in exchange for (potentially greatly) inflating the number of functions in the API itself).

[1] https://groups.google.com/d/msg/golang-nuts/NWMReL1HueQ/X9mdYduCOB8J

[2] http://stackoverflow.com/questions/2032149/optional-parameters

@graydon
Copy link
Contributor

graydon commented Aug 12, 2013

I should also point out that it interacts with closures and taking first class references to functions.

@Seldaek
Copy link
Contributor

Seldaek commented Aug 12, 2013

@bstrie thanks for the overview of Go. I agree with the point that overloading is probably a bad idea because it creates confusing APIs. Similarly the "overloaded" methods of jQuery that can take a callback in pretty much any argument you want are very weird and a pain to implement.

And I sort of agree that wrapper functions to fill in default args are not so bad in some cases. But the problem is while it saves you from some confusing cases, it does introduce a lot of boilerplate in others. One example is *::to_str_radix(num, radix). If we had optional args this could well be folded in *::to_str(num, radix = 10). I feel that in some cases the explosion of function variants really makes things less intuitive and harder to remember. Just my two cents though obviously.

@dobkeratops
Copy link

I can see its a good time trade-off to defer them;
but does go really omit defaults philosophically, or is that just them justifying the implementation time-budgetting trade-offs they made? I'd be surprised if anyone thought wading through long_function_names_with_lots_of_trailing_qualifiers is a step forward :) , but if they said "it lets us leverage simple tools, we haven't developed have a powerful IDE yet.." thats another matter.

I definitely miss both overloading and defaults from C++ .. but I've been happy to trade them for Rusts' other conviniences and the promise of a C++ alternative (after being stuck with 1 main language for so long..)

For an experiment I thought about trying to do them as a 'parser hack' ... working at the level of macros? inlining the default expr's at the call sites where arguments are missing?
I realise thats unlikely to be an acceptable design - but it makes me beleive its not impossible to have default args that work with rusts' features.

One thing i'm still really getting used to is that methods are in theory much more useable in rust.
Back in C++ I have a fear of them because of header/dependancy issues,(this is the main reason i'm here), so I often instinctively go for functions first.
Perhaps i'll miss defaults less when i'm using rust better..

@pnkfelix
Copy link
Member

Just so the idea doesn't disappear: the chatty aspect of bar(Foo{y: 5, ..Foo::default()}) seems to me to be the ..Foo::default() part.

If we made it easier to leave out fields in a struct construction, letting them fall to a default, then maybe this approach would be more palatable.

E.g. a new form, probably defined solely for structures that implement some appropriate trait (Zero or Default or whatever), that looked like this:

Foo{y: 5, *) where the * takes the place of the earlier ..Foo::default().

Then the example is "just" bar(Foo{y: 5, *}). Maybe others would prefer the Foo not being there either, but this seems pretty clean to me.

@dobkeratops
Copy link

I suppose enums also give you some other ways of conviniently passing groups of optional params differently to what a C++ programmer is used to ... and a way of interspersing annotation & values with a call.
That would seem to suit the cases like creating something with lots of setup parameters well

@bstrie
Copy link
Contributor

bstrie commented Aug 13, 2013

@pnkfelix Remember that the Foo struct in that example would probably have a much more self-descriptive name, and even with your proposed sugar would look something like this in practice:

html::start_server(html::ServerOpts{port: 10088, *});

It does look slightly nicer. Still not certain if it helps enough to warrant new syntax, or if it suffices to satisfy all use cases of default arguments.

In fact, to use my own example, when initializing something with a lot of options such as an HTML server I'd probably be just as happy setting up a server_opts struct beforehand and then just calling start_server(server_opts). And I really don't mind adding a line with ..html::ServerOpts::default if my struct initialization already spans several lines.

I think perhaps we need to rethink where default arguments would be most useful. For me, it's not where there's a lot of potential arguments; these should be fairly rare, and a few lines to init a configuration struct is fine. Rather, I most miss default arguments when 1) the operation is fairly common, and 2) the operation has, at most, one or two arguments that are almost always superfluous but required for completeness. For example:

let foo = Some('a');
foo.unwrap();  // same as today
foo.unwrap('z');  // imagine that this replaced unwrap_or_default

int::from_str("4");  // same as today
int::from_str("4", 16);  // imagine that this replaced from_str_radix

However, now that I've typed these out, I actually prefer the explicitness of the separate methods. :) Maybe I don't really know what I want after all!

@dobkeratops, from your experience with C++, can you give us some examples of nice C++ APIs that are enabled by default arguments?

@bstrie
Copy link
Contributor

bstrie commented Aug 13, 2013

Comments in https://github.com/mozilla/rust/wiki/Meeting-weekly-2013-08-13 seem to indicate that the devs see this as a far-future feature. Nominating.

@tautologico
Copy link
Contributor

OCaml does optional/default argument without overloading, I believe. Optional arguments of type T without a default value are implicitly converted to T option and the function must check if a value was provided. There are also restrictions on the declaration and use of optional arguments to make the compiler know when an argument has been omitted. All optional arguments must have labels (they must be keyword arguments in order to be optional). This complicates OCaml's type system (labels become part of the type of the function) but I think this is an artifact of having to interoperate with the rest of the Hindley-Milner type system.

@pauljurczak
Copy link

Coming from C++, I would really miss default argument values. I often use functions with large number of parameters, where most of them almost always have default values, except of some rare use or test cases. Adding purely combinatorial function name derivatives in lieu of default values, would create a big mess of really long names.

@darkf
Copy link
Contributor

darkf commented Aug 14, 2013

I agree that default arguments and/or keyword arguments would be extremely useful. I think we should work towards a pre-1.0 proposal that outlines what needs to be done in terms of overloading and perhaps have a discussion on the syntax.

@dobkeratops
Copy link

Good to see more people commenting in favour :) Another reason I wanted it was to increase the amount of C++ APIs that could be translated seamlessly. Using C++ libraries is one big reason why we're stuck with C++.

I suspect the only way we'd get this if the community could implement it without any cost to the core developers.
I agree they've got way more important things to do, & enums recover lost polymorphism with possibilities not immediately apparent to C++ conditioned brains.

This is why i wanted to submit the groundwork on parsing it into the AST data-structure. It would be ready for someone else to pick it up and try to get a solution. initial feedback discouraged me from making a PR.

from c++ I look at what scala and python have on this with envy. A native non-GC language with that elegance would be great.
..its more functionality in one place, less navigating to figure out what's going on, less nesting depth, less noise in the sourcecode. Not just trivially less typing.
maybe you could even claim its more self-documenting, the function signature tells you more about how its used.

the difficult bit is the type checker, preventing subtle bug possibilities and confusing errors if it was just done as a parser hack, it think.

imagine if you could just write things like this..

fn substr(a:&str, start:int, end:int=a.len())->~str

just hack that sort of thing into the AST without error checks, i'm sure a lot can go wrong .. but imagine if we could fix that to do what was expected :)

@catamorphism
Copy link
Contributor

Just a bug. We can debate the merits, but won't block a release on it.

@olivren
Copy link
Contributor

olivren commented Sep 7, 2013

I like the behavior that @tautologico describes in his comment.

Applied to Rust, I believe this would translate to the following:

fn ga(bu: int, zo: Option<int>, meu: Option<int>) {
  let zo = zo.get_or_default(42);
  let meu = meu.get_or_default(99);
  ...
}

And these would all be valid calls :

ga(10, 20, 30); // 20 and 30 are automagically
                // converted to Some(20) and Some(30)
ga(10, 20);         // ga(10, 20, 99) 
ga(10);             // ga(10, 42, 99)
ga(10, None, None); // ga(10, 42, 99)
ga(10, 20, None);   // ga(10, 20, 99)
ga(10, None, 30);   // ga(10, 42, 30)

The rule is that the trailing Option<T> parameters can be omitted. Here, the Option type is reused, but if this cause some problems another more specific OptParam type could be created.

This proposal allows the caller to choose precisely the parameters he wants to provide and the ones he wants to keep defaulted, independently of its position. Also, I think it's a good thing that the default value does not appear in the parameter list. This way, the default value is not limited to a constant expression, it can depend on the state of the object, or it can depend on other parameters.

Real life example of a function that shows a GUI dialog box :

fn showDialog(message: ~str,
              parent: Option<Widget>,
              title: Option<~str>,
              type: Option<DialogType>,
              icon: Option<Icon>) { ... }

// Display an info box in the middle of the screen.
// Set the title to "information", translated in the current locale
// Set the icon to an "info" icon
showDialog(~"hello, world!");

// Display a warning box in the middle of the screen.
// Set the title to "warning", translated in the current locale
// Set the icon to a "warning" icon
showDialog(~"sick, sad world!", None, None, WarningDialog);

// Display a warning box in the middle of the screen.
// Set the title to "warning", translated in the current locale
// Set the icon to a custom icon
showDialog(~"sick, sad world!", None, None, WarningDialog, bugIcon);

In this example:

  • the default value of parent is None (no parent)
  • the default value of type is InfoDialog
  • the default value of icon depends on the value of type
  • the default value of title depends on the value of type and on the current locale

@thestinger
Copy link
Contributor

You're now also proposing adding Option as a new primitive type. It's entirely a library feature right now, as it's a plain old enum.

@acertain
Copy link

acertain commented Sep 8, 2013

I'd like to add that IMO default args are a billion times better with keyword args. I don't like the idea of default args but no keyword args.

@dobkeratops
Copy link

one is a stepping stone to the other and defaults are stilll useful on its own, you have some extra flags.. and can leave them off if you just want the defaults..
agreed they combine well if you have both.
The stickig point is actually implementing them :)
how to retrofit it into the typechecker/type inferer.. or how else to slot it in.

@bluss
Copy link
Member

bluss commented Sep 9, 2013

Default values for positional arguments results in cryptic code. Why not only have defaults for keyword arguments? (If these are implemented).

@catamorphism
Copy link
Contributor

I'm adding the "far-future" milestone to emphasize the extreme discouragement that we on the core team wish to communicate to anybody spending a second of time on this issue. If you want to contribute to Rust right now, please tackle one of the 41 open bugs on milestone 1 (well-definedness):

https://github.com/mozilla/rust/issues?milestone=12&state=open

or one of the 104 open bugs on milestone 2 (backwards-compatibility):

https://github.com/mozilla/rust/issues?milestone=13&state=open

or one of the 68 open bugs on milestone 3 (features we agreed at one point are crucial for releasing Rust 1.0):

https://github.com/mozilla/rust/issues?milestone=14&state=open

or even just comment on one of those bugs to ask for clarification or suggest ways to make progress. That's 213 bugs to choose from; making progress on any one of them at this point would be much more valuable to Rust than this issue. And anyone who can close one of these bugs will have our utmost gratitude.

@dobkeratops
Copy link

I can see the core team has way more important things to do but it seems a shame to 'communicate extreme discouragement' :(
+1 to vallorics comments. naming is hard,digging through documentation and learning more names is offputting..; default args/overloarding make it easier; with keyword args you'd have an opportunity to go beyond C++ on this. (I wish github had voting, i could just hit +1 instead of ranting a bit more here lol)

@Seldaek
Copy link
Contributor

Seldaek commented Oct 7, 2013

@Valloric I entirely agree (as I said before), and really hope the rust core team reconsiders or at least re-discusses the optional args because that impacts API design. Named args can definitely wait since they mostly impact the call-site (of course the name of args becomes part of the API design once we have named args, so it does have a slight impact but not a BC-breaking one if the args names are cleaned up when named args are introduced).

@thestinger
Copy link
Contributor

There isn't consensus that this would be a good language feature. There's also no concrete proposal with all the details worked out for named/default parameters. It's not going to happen for 1.0, because there's an existing set of core languages features to either fix or remove before dreaming up very unessential syntactic sugar.

@dobkeratops
Copy link

'dreaming up' makes it sound like its something fanciful, but these features have been proven in other languages. Its not just syntax sugar - its reducing the amount of code you have to navigate through, reducing the number of symbols you have to learn

@Valloric
Copy link
Contributor

Valloric commented Oct 7, 2013

It's not going to happen for 1.0, because there's an existing set of core languages features to either fix or remove before dreaming up very unessential syntactic sugar.

I'm not saying that the other language features that need to be fixed/implemented are not more important; they probably are. But it's not either/or. All I'm saying is that this feature should be strongly considered for 1.0 (in addition to the other features) because not having it impacts the quality of the APIs in the std library forever.

This is probably a controversial opinion, but IMO programming languages live and die more by the APIs they provide than the core features in them. Python's "batteries included" std library sold the entire language. CPAN keeps Perl alive. .Net makes writing C# code awesome, and LINQ is the best thing since sliced bread. One of C++'s greatest failings is the lack of good, standardized APIs. Etc.

The APIs are critical to the success of a language so features that enable the creation of good APIs shouldn't be discarded as "unessential syntactic sugar".

@thestinger
Copy link
Contributor

@dobkeratops: by saying dreaming up, I'm trying to emphasize that no complete proposal has been made so it's not at a decision-making step

@catamorphism
Copy link
Contributor

This discussion is unproductive. To prevent it draining any more of anyone's time, I'm going to close the issue. If somebody wants to reopen it (or start a new issue) a year from now, or later than that, that would be fine.

@thestinger
Copy link
Contributor

If someone comes up with a proposal with almost all of the details worked out (grammar, semantics), I suggest posting it to the mailing list. If consensus is reached about a certain way of doing this being the right way, then it makes sense to open an issue and implement it as an experimental feature behind a flag like once functions.

@catamorphism
Copy link
Contributor

I second @thestinger -- if someone (or a small group of people) has a complete proposal, or a proposal with a few clearly-spelled-out blank spots that are up for discussion, it would be appropriate for that person to run it by the mailing list. That's no promise that we'll implement that proposal, but doing the work of formalizing the idea and explaining how it interacts with other language features would increase the value of the suggestion greatly.

@Valloric
Copy link
Contributor

Valloric commented Oct 8, 2013

@thestinger @catamorphism Thank you both for keeping an open mind! When I find some time I'll prepare a proposal and send it to rust-dev.

@KokaKiwi
Copy link

I created a pad in order to debate about this feature and to write a clear spec about it: https://pad.riseup.net/p/hvbg6dQQnEe7

@brson
Copy link
Contributor

brson commented Oct 21, 2013

I'm reopening this. It's not a priority - there's already far too much that needs to be done - but it is a feature that people want, and that isn't completely off the table. I don't wish to shut down any conversation on the subject.

@brson brson reopened this Oct 21, 2013
@Valloric
Copy link
Contributor

@brson Thank you!

@bstrie
Copy link
Contributor

bstrie commented Oct 21, 2013

I've edited the original issue with a link to the etherpad.

@KokaKiwi
Copy link

KokaKiwi commented Nov 3, 2013

Hi.
Since the pad creation, it has been completed by many design propositions, questions, problems, etc...
So I've created a second pad to "summarize" the discussion pad, this pad will be used to describe the feature request exactly.
Pad URL here: https://pad.riseup.net/p/Ca5PBeDjUGxW

@emberian
Copy link
Member

@KokaKiwi all of the pads are empty now. Where did the contents go?

@huonw
Copy link
Member

huonw commented Dec 30, 2013

@cmr, I guess:

WARNING: This pad will be DELETED if 30 days go by with no edits. There is NO WAY to recover the pad after this happens, so be careful!

😦

@KokaKiwi
Copy link

I'm actually searching the pad I created on Mozilla Etherpad instance (in order to prevent this case), but I can't find it in my history, and I forgot to publish the link here :(

@dram
Copy link

dram commented Dec 31, 2013

@KokaKiwi
Copy link

@dram That's what I was looking for! Thanks 😃
Maybe the issue should be edited with the new links, I think.

@huonw
Copy link
Member

huonw commented Dec 31, 2013

(Edited.)

@chrisballinger
Copy link

I don't have anything meaningful to add other than this is one of the things I'm watching closely before I invest more time with the language. Coming from languages (Objective-C and Python) that have this feature, I believe that named parameters are an indirect major boost to productivity because of how they force other people's code to be more readable.

@thestinger
Copy link
Contributor

A concrete proposal should be done through the new RFC process: https://github.com/rust-lang/rfcs

There are too many conflicting ideas and diverging threads of conversation here for it to be a useful discussion area.

flip1995 pushed a commit to flip1995/rust that referenced this issue Apr 21, 2022
ignore `&x | &y` in unnested_or_patterns

replacing it with `&(x | y)` is actually more characters

Fixes rust-lang#6973

changelog: [`unnested_or_patterns`] ignore `&x | &y`, nesting would result in more characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests