Skip to content

Default values for destructuring parameters #1558

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

Closed
timoxley opened this issue Jul 31, 2011 · 29 comments
Closed

Default values for destructuring parameters #1558

timoxley opened this issue Jul 31, 2011 · 29 comments

Comments

@timoxley
Copy link

What I'm proposing is the ability to supply default values for parameter lists (note the {}):

# this works:
constructor: (name, description = 'no description') ->

# so does this:
constructor: (@name, @description = 'no description') ->

# this does not works:
constructor: ({name, description = 'not described'}) ->

# nor this:
constructor: ({@name, @description = 'not described'}) ->

I was surprised when this didn't compile, considering you can set defaults for regular named arguments, and also destructuring arguments (i.e. all types of named arguments except parameter lists).

It'd also be nice to be able to describe parameter lists over multiple lines, but I couldn't think of a way to do it without making it rather ({ugly}), eg:

constructor: ({
    @name
    @description 
}) ->

edit: the above actually works.

Additionally, the destructuring parameter lists syntax isn't mentioned in the docs anywhere, and the syntax is really neat:

class Man
    constructor: ({@name, @description }) ->

dave = new Man
    name: 'Dave'
    description: 'a dude'
console.log "#{dave.name} is #{dave.description}"

edit: rewritten to use the term 'destructuring parameter lists' instead of 'unknown auto-assigned option things huh?'.
edit 2: clarified the ticket, as I didn't actually understand what I had written myself.

@timoxley
Copy link
Author

Is this a silly request?

@michaelficarra
Copy link
Collaborator

Not at all. It's certainly consistent. I think it may be a little "too much", but I can't really define that term. At the very least, you've notified us that destructuring parameter lists are undocumented.

@timoxley
Copy link
Author

thanks. I guess what I'm trying to say is:
It would be nice to be able to transparently refactor between unnamed and named parameter lists without having to hand-write default parameter handing logic.

Sometimes we end up adding many new parameters to functions, when they should probably be promoted to named parameter lists (ever seen a function that takes 5+ unnamed arguments??). Some people may hold off on making the switch to named parameters if they have to sacrifice the succinctness of being able to supply default params within the function signature.

Though in saying that, it's not going to look so succinct unless the parameter list can be specified over multiple lines, in which we'd have to agree on some new syntax which doesn't look gross, and in that case perhaps handwriting defaults for parameter lists isn't really so bad afterall. :/

@michaelficarra
Copy link
Collaborator

unless the parameter list can be specified over multiple lines. :/

which it can be

@timoxley
Copy link
Author

whoa, how did I miss that? It can too, just isn't picked up by the textmate syntax highlighter, and is a bit bracey, well… my bad.

@timoxley
Copy link
Author

Given that, I'm back on the 'ability to provide defaults would be good' bandwagon.

@michaelficarra
Copy link
Collaborator

I almost forgot: the main proposal in this issue is very closely related to #1162 (which expresses my above-referenced complexity concerns pretty well) and #810. I want to make sure we don't forget about documenting destructuring parameters if we decide to close this, so can you open a separate issue for that?

@timoxley
Copy link
Author

Separate issue created for undocumented destructuring parameters

I'd agree with the conclusion in other thread, all of the proposed solutions in that other thread are ridiculous and complex, this is coffeescript, not regex/perl.

The idea here is to keep the simple syntax and functionality consistent between named and unnamed params. I'd argue that this proposal in fact reduces complexity; you have a single syntax with the same rules for both approaches to parameters.

Perhaps there are aspects that complicate things I am not aware of though.

@goatslacker
Copy link

+1

It's a commonly used pattern and having default values will make it much sweeter than writing your own mixin function.

@gampleman
Copy link

So how should that compile? Would it allow nesting? Would it work elsewhere then in function declarations? If not, then what about consistency?

@codelahoma
Copy link
Contributor

Hacky workaround that currently works:

constructor: ({name, description}, description = "not described") ->

@michaelficarra
Copy link
Collaborator

@codelahoma: that code is discouraged. See #1002, #1547. Identical parameter names will be a syntax error soon.

@s4y
Copy link

s4y commented Dec 18, 2012

+1

@vendethiel
Copy link
Collaborator

Please don't bump after a year. I remember this syntax worked great in Coffee 1.3 but got broken/removed in Coffee 1.4

@s4y
Copy link

s4y commented Dec 19, 2012

@Nami-Doc Is there a better bug to poke? I started using CoffeeScript pretty recently so I don’t know what was different in CoffeeScript 1.3. I noticed that this works:

(foo = bar, { baz }) ->

…this doesn’t:

(foo, { baz = bar }) ->

…and it seems to match what the OP brought up.

@vendethiel
Copy link
Collaborator

I remember an issue bringing this regression up but I wasn't able to find it back. Will look again later

@00dani
Copy link

00dani commented Feb 14, 2013

+1 for default values working in object destructuring. I expect:

f = ({name = "Fred"}) ->
  console.log name
# to be equivalent to
f = (args) ->
  {name = "Fred"} = args
  console.log "Fred"
# and equivalent to
f = ({name}) ->
  name ?= "Fred"
  console.log name

This essentially gives CoffeeScript "proper" keyword arguments: function parameters that can be invoked by name in any order, and may be left out to use a default instead.

(Okay, technically the middle version isn't 100% equivalent because it also leaves a variable called args lying around, but the point is that {field = default} = object should work just like it also should in parameter lists.)

@danschumann
Copy link

+1

On Wed, Feb 13, 2013 at 8:31 PM, 00Davo [email protected] wrote:

+1 for default values working in object destructuring. I expect:

f ({name = "Fred"}) ->
console.log name# to be equivalent tof ({name}) ->
name ?= "Fred"
console.log name

This essentially gives CoffeeScript "proper" keyword arguments: Function
parameters that can be invoked by name in any order, and may be left out to
use a default instead.


Reply to this email directly or view it on GitHubhttps://github.com//issues/1558#issuecomment-13530253.

@00dani
Copy link

00dani commented Feb 14, 2013

On testing out the behaviour of that third snippet:

f = ({name}) ->
  name ?= "Fred"
  console.log name

I find it works perfectly if you call f with an object, even an empty object f {}, but it fails if you try to call f without any arguments f(): It attempts to access _arg.name without testing if _arg is non-null. Argumentless calls probably should work, especially when all the parameters the object would hold have default values, so pattern-matching against an object should use safe navigation, as in _arg?.name.

@epidemian
Copy link
Contributor

Argumentless calls probably should work

Mmm... i'd prefer they didn't. As of now, argument destructuring is only a handy shortcut to say "i don't care about this n-th parameter as a whole object, i just care about these properties". The specific properties can be undefined, but not the object itself.

You can make the call work without any arguments by specifying a default value for that parameter, just like any other parameter:

f = ({name} = {}) ->
  name ?= "Fred"
  console.log name

You can even do:

defaults = 
  name: 'Fred'
f = ({name} = defaults) ->
  console.log name

I'm partially in favour of this proposal though. Being able to specify default values for destructuring arguments seems pretty handy. I'm pretty sure though that this same topic has been discussed here before; and that the discussion turned out lengthy and dragging (thanks in part to me xD).

@00dani
Copy link

00dani commented Feb 15, 2013

Ah, {name} = {} didn't occur to me, but now you've mentioned it I think it's a good approach; it doesn't have much overhead (typing three extra characters is hardly a problem), intentions are clearer, and it's consistent with how arguments normally work. I'm now satisfied that argumentless calls don't need special handling.

Object destructuring still would be much better with defaults, though.

@epidemian
Copy link
Contributor

I found the old discussion. It's lengthy, but i just wanted to make a reference not to repeat myself (too much 😝 ).

I'm glad we're on the same boat about object-destructuring parameters not implicitly defaulting to an empty object, @00Davo 😃

Digging through the issues i also found that @jashkenas was not in favour of having destructuring parameters and complicated parameter list expressions (#362). I wonder why have his opinion changed though (if it actually has changed); seeing some recent issues about parameter lists being confused with other expressions, i kinda wish the decision to keep parameter expression as simple identifiers would've held.

@jashkenas
Copy link
Owner

i kinda wish the decision to keep parameter expression as simple identifiers would've held.

Me too.

@michaelficarra
Copy link
Collaborator

I completely disagree. A parameter list its just an implicit positional destructuring assignment of arguments. It should support all of the LHSs assignment does. Defaults are in a category of their own, though. I don't care whether they stay or go.

@lydell
Copy link
Collaborator

lydell commented Aug 16, 2013

According to this blog post I just stumbled upon, ES6 is going to have defaults in destructuring (look for the heading called "Default Values"):

You can also provide default values for when the property you are destructuring is not defined:

 var [missing = true] = [];
 console.log(missing);
 // true

 var { x = 3 } = {};
 console.log(x);
 // 3

[…] we can also give default values to the properties of the objects we are destructuring. This is particularly helpful when we have an object that is meant to provide configuration and many of the object's properties already have sensible defaults. For example, jQuery's ajax function takes a configuration object as its second parameter, and could be re-written like this:

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

This avoids repeating var foo = config.foo || 'default foo'; for each property of the configuration object.

From the CoffeeScript documentation:

CoffeeScript implements ECMAScript Harmony's proposed destructuring assignment syntax

According to the same blog post:

TC39 (the governing committee of ECMAScript) has already reached consensus on destructuring assignment and it is part of the draft ES6 specification. Effectively what this means is that it is now up to the people writing JavaScript engines to start implementing it; SpiderMonkey (Firefox's JS engine) already has support for much of it. Track SpiderMonkey's destructuring (and general ES6) support in this bugzilla ticket.

If that blog post is right, I'd say: Nobody cares about that old Harmony proposal anymore: Update the docs! Either say that CoffeeScript destructuring is like ES6 destructuring, except that defaults are not possible, since they are considered too complex or something (just like loose comparison via JS's == isn't possible in CoffeeScript, since it "frequently causes undesirable coercion, is intransitive, and has a different meaning than in other languages"), or add defaults to the language and say that there is no difference at all (I'd vote for the latter).

@00dani
Copy link

00dani commented Aug 17, 2013

+1 on mimicking destructuring defaults from ES6. They're hardly a "considered harmful" feature that CoffeeScript should inhibit use of, like with and ==; on the contrary, destructuring defaults on a parameter object correspond to idiomatic JS design.

@lydell
Copy link
Collaborator

lydell commented Aug 25, 2013

Note that ES6 destructuring defaults isn't just about function parameters—it works anywhere. For example, this

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

could be replaced with

jQuery.ajax = function (url, options) {
  var {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true,
    // ... more config
  } = options
  // ... do stuff
};

if that's considered more readable.

This issue is called "Default values for destructuring parameters", while what I propose would be simply "Default values for destructuring".

@00dani
Copy link

00dani commented Aug 25, 2013

Yep, CoffeeScript's destructuring should be consistent between parameter lists and regular assignments, especially given that ES6's works in both contexts.

Mirroring the ES6 support for default values in all destructuring patterns seems a good move.

@lorensr
Copy link

lorensr commented Aug 11, 2015

I currently use

fn = ({foo, bar} = {foo: 3}) ->

and definitely would prefer

fn = ({foo = 3, bar}) ->

Note that for the second to be equivalent, I must be able to call fn() and not get TypeError: cannot read property 'bar' of undefined (which is what currently happens when fn = ({bar}) -> and you call fn()).

lydell added a commit to lydell/coffee-script that referenced this issue Aug 22, 2015
This let's you do things like:

    fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"

Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.

    fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
    assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)

Fixes jashkenas#1558, jashkenas#3288 and jashkenas#4005.

A test that lone expansions `[...]=a` are prohibited failed with this commit.
However, the error message used to be really weird and shows that the test just
_happened_ to pass:

    $ coffee -bpe '[...]=a'
    [stdin]:1:2: error: Expansion must be used inside a destructuring assignment or parameter list
    [...]=a
     ^^^

That _is_ inside a destructuring assignment. Since `[]=a` is allowed, I figured
the easiest and most consistent way would be to make `[...]=a` equivalent to
`[]=a`.
lydell added a commit to lydell/coffee-script that referenced this issue Aug 22, 2015
This let's you do things like:

    fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"

Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.

    fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
    assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)

Fixes jashkenas#1558, jashkenas#3288 and jashkenas#4005.

A test that lone expansions `[...]=a` are prohibited failed with this commit.
However, the error message used to be really weird and shows that the test just
_happened_ to pass:

    $ coffee -bpe '[...]=a'
    [stdin]:1:2: error: Expansion must be used inside a destructuring assignment or parameter list
    [...]=a
     ^^^

That _is_ inside a destructuring assignment. Since `[]=a` is allowed, I figured
the easiest and most consistent way would be to make `[...]=a` equivalent to
`[]=a`.
lydell added a commit to lydell/coffee-script that referenced this issue Aug 23, 2015
This let's you do things like:

    fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"

Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.

    fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
    assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)

Fixes jashkenas#1558, jashkenas#3288 and jashkenas#4005.
lydell added a commit to lydell/coffee-script that referenced this issue Aug 23, 2015
This let's you do things like:

    fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"

Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.

    fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
    assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)

Fixes jashkenas#1558, jashkenas#3288 and jashkenas#4005.
lydell added a commit to lydell/coffee-script that referenced this issue Aug 27, 2015
This let's you do things like:

    fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"

Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.

    fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
    assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)

Fixes jashkenas#1558, jashkenas#3288 and jashkenas#4005.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests