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

Function keyword arguments? #2427

Closed
leeola opened this issue Jul 8, 2012 · 44 comments
Closed

Function keyword arguments? #2427

leeola opened this issue Jul 8, 2012 · 44 comments

Comments

@leeola
Copy link

leeola commented Jul 8, 2012

This seems to have been raised a hundred times before, but the answer most are pointing to is very very old.. found here, on issue 49.

So pardon me if i missed an answer in all the noise, but it sounds like the biggest opposition to the original keyword arguments/parameters was that = visually cluttered things up when combined with =>.. but that's obviously not the case anymore, since we currently use foo = (bar='bar', baz='baz') =>. So why can't we use keyword based arguments?

To be clear, i am talking about the ability to give a functions arguments out of order, based on keywords. For example, using the function from above: foo = (bar='bar', baz='baz') => console.log "Bar:#{bar}, Baz:#{baz}" i would like the ability to call the function with out of order arguments, such as: foo baz='baz', bar='bar'.

The reason i feel this is rather important, is because i don't believe (please, correct me if i am wrong) there is a good way to handle out of order arguments in CoffeeScript. If you have a function that takes 4 arguments, and a 5th as a callback, but you want to allow for a use case where async_stuff callback is an acceptable format, your code can get ugly pretty quick.. and it seems needless. Keyword Arguments could open up a lot of functionality and maintain quality code at the same time.

Thoughts?

@gkz
Copy link

gkz commented Jul 8, 2012

f = ({x, y}) ->
   x + y

f x: 3, y: 2 #=> 5
f y: 2, x: 3 #=> 5

You can't have default values combined with that though, if you want that you'll have to add some stuff in your function body like x ?= 2

@leeola
Copy link
Author

leeola commented Jul 8, 2012

Well.. yea, but it's all sort of a workaround to implement a missing feature. As you point out, the current solutions all have downsides.

The solutions are basically..

  1. to add in a dict(object) parameter. The downside to this is that you have to force the user to use foo {param2: val2, param1: val1}
  2. use the method you point out, which destroys CoffeeScript's default argument feature
  3. use normal arguments and defaults, and do a ton of type checking.. this is ugly, and potentially very error prone.

@mark-hahn
Copy link

How would you implement this?

@bluepnume
Copy link

foo = (bar=1) -> console.log(bar)
eggs = (ham=2) -> console.log(ham)
foo = eggs
foo(ham=3)

How do we deal with situations like this?

@mark-hahn
Copy link

Could someone please give a small example of the compiled output for the
caller and the callee? I may be missing something but I don't see how this
could possibly be implemented.

On Mon, Jul 9, 2012 at 12:29 PM, bluepnume <
reply@reply.github.com

wrote:

foo = (bar=1) -> console.log(bar)
eggs = (ham=2) -> console.log(ham)
foo = eggs
foo(ham=3)

How do we deal with situations like this?


Reply to this email directly or view it on GitHub:
#2427 (comment)

@erisdev
Copy link

erisdev commented Jul 9, 2012

@leeolayvar

  1. The curly brackets are totally optional in that case. Implicit objects. foo param2: val2, param1: val1
  2. The method gkz pointed out is actually syntactic sugar for the same thing as CoffeeScript in Rails #1. If you need default parameters, you'll have to resort to the JavaScripty method of x ?= defaultX &c. Which isn't that bad, honestly.
  3. Agree, which is why you shouldn't do that. Arguments should be either positional or keyed, never type-sniffed. Never mind what the other kids are doing.

@mark-hahn it can't!

@leeola
Copy link
Author

leeola commented Jul 9, 2012

@bluepnume

How do we deal with situations like this?

I don't think we would.. foo = eggs; foo('bar') is simply calling eggs('bar').

@erisdiscord

The method gkz pointed out is actually syntactic sugar for the same thing as #1. If you need default parameters, you'll have to resort to the JavaScripty method of x ?= defaultX &c. Which isn't that bad, honestly.

"Which isn't that bad, honestly." well no.. but it's bad enough that default arguments were added into CoffeeScript.

@mark-hahn it can't!

@erisdiscord That seems like a bold statement lol.

The point of this post was to open up discussion about this. If i had already implemented it, i'd be asking for a pull haha. The idea for this obviously comes from other languages that already implement this feature set, but i realize there are core differences in the language that makes it potentially rough to implement.

Because ES5 doesn't support this (ES6 will, thankfully) we don't have many options. The only way i can think of, off hand, is not graceful.. not to mention, the existing implementation of positional parameters having defaults causes trouble when telling the compiler what type of parameter something is, positional or keyword.

However, i think it would be possible if we gave every function a hidden object as the first parameter. This parameter would be what the compiler would send incoming keyword arguments in as.

A function definition in CoffeeScript could look something like this..

foo = (pos1, pos2, kw1=null, callback=->) ->
  callback()

foo 5, 'bar', callback=->
# or even..
foo callback=->console.log 'Hello Callback'

and would compile to something like this..

var foo;
foo = function (_keywords, pos1, pos2) {
  var _default_keywords, kw1, callback
  _default_keywords = {
    kw1: null,
    callback: function(){}
  }
  safe_merge(_keywords, _default_keywords)

  callback = _keywords.callback
  kw1 = _keywords.kw1

  callback()
}


foo({callback: function(){}}, 5, 'bar')
// or even..
foo({callback: function(){ console.log('Hello Callback') }

Note that this implementation does not account for positional usage of keyword arguments, but that shouldn't be difficult. This would allow for the user to call functions in a similar method as that of Python.

Thoughts?

edit1: Fixed formatting. And also, my apologies @erisdiscord. when i said that was a "bold statement", i hadn't seen what you were replying to. If you were replying to @bluepnume's code example.. i too believe it would be impossible.. or at the very least, it wouldn't be implemented.

edit2: More formatting.

@leeola
Copy link
Author

leeola commented Jul 9, 2012

The more i think about this, the more i think it's actually rather nice.. and is about as elegant as i could think to add this feature.

The downside would be that this method is creating an object for every function, which may cause an unnecessary impact on performance. I have no idea what the performance implications are for an implementation such as this.

@erisdev
Copy link

erisdev commented Jul 10, 2012

@leeolayvar I'm afraid that would break compatibility with JavaScript libraries in both directions. JavaScript code would have to call CoffeeScript functions with a keyworded first argument and...I'm not altogether sure how CoffeeScript code would be able to call outside JavaScript functions.

The more I think about it, personally, the more I think default arguments in CoffeeScript are a poor fit. In Ruby, it allows you to omit an argument when calling a function, but JavaScript already lets you do that anyway.

Regarding the bold statement, it seemed like you were asking for all arguments to be passable either positionally or by keyword, and there's still just no good way to do that without breaking interoperability somehow.

@leeola
Copy link
Author

leeola commented Jul 10, 2012

@erisdiscord

I'm afraid that would break compatibility with JavaScript libraries in both directions. JavaScript code would have to call CoffeeScript functions with a keyworded first argument and...I'm not altogether sure how CoffeeScript code would be able to call outside JavaScript functions.

Agreed, it was only off the top of my head. This was all about discussion :)

I would like to point out though, all languages eventually need to move on. Perhaps this should be kept for the ES6 discussion? Seeing as ES6 already has keyword arguments, and is one of the many ES6 features that are already usable on Nodejs (if i recall correctly), CoffeeScript will eventually need to support Keyword Arguments, to stay compliant with JavaScript.

Regarding the bold statement, it seemed like you were asking for all arguments to be passable either positionally or by keyword, and there's still just no good way to do that without breaking interoperability somehow.

Well i thought you're comment was in reply to @mark-hahn's comment, which appeared to be in reply to @bluepnume's comment.. which is indeed, impossible hehe.

At any rate, as no one else seems to have any forward moving ideas, i think this case is settled :)

Bring on ES6 compliant CoffeeScript, and fast! hehe :)

gkz added a commit to gkz/LiveScript that referenced this issue Jul 11, 2012
@gkz
Copy link

gkz commented Jul 11, 2012

I've added this feature to LiveScript (on master, haven't released a new version yet).

Example:

add = ({x = 4, y = 3}) ->
  x + y

add y: 9 #=> 13

Which compiles to:

var add;
add = function(__arg){
  var x, y, __ref;
  x = (__ref = __arg.x) != null ? __ref : 4, y = (__ref = __arg.y) != null ? __ref : 3;
  return x + y;
};
add({
  y: 9
});

@leeola
Copy link
Author

leeola commented Jul 11, 2012

Interesting, does it take positional arguments too? My only question would be on how flexible it is.. Could you use both positional & keywords with that syntax?

@gkz
Copy link

gkz commented Jul 11, 2012

It doesn't matter which order you put them in.

@Xavura
Copy link

Xavura commented Jul 12, 2012

Perhaps if one could use de-structuring assignment along with conditional existence, it would make specifying default parameters much nicer. That combined with the feature mentioned in the first comment would give us:

example = ({foo, doStuff}) ->
    [foo, doStuff] ?= ['default', false]

Maybe even...

example = ({foo, doStuff} ?= ['default', false]) ->

Might be a terrible idea... it just popped into my head so I thought I'd share.

@gkz
Copy link

gkz commented Jul 12, 2012

[foo, doStuff] ?= ['default', false] 

already does what you want in LiveScript.

add = ({x = 4, y = 3}) ->
  x + y

is just sugar for

add = ({x ? 4, y ? 3}) ->
  x + y

@Xavura
Copy link

Xavura commented Jul 12, 2012

Nice, I like how you implemented it.

👍

@gkz
Copy link

gkz commented Jul 12, 2012

You should thank @satyr - I inherited these features from coco (the ({x = 4}) -> sugar is mine)

@satyr
Copy link
Collaborator

satyr commented Jul 12, 2012

example = ({foo, doStuff} ?= ['default', false]) ->

Already possible:

$ coffee -bce '({foo, doStuff} = {foo: "default", doStuff: false}) ->'
// Generated by CoffeeScript 1.3.3

(function(_arg) {
  var doStuff, foo, _ref;
  _ref = _arg != null ? _arg : {
    foo: "default",
    doStuff: false
  }, foo = _ref.foo, doStuff = _ref.doStuff;
});

@vendethiel
Copy link
Collaborator

But this requires to pass either NO param or ALL params

@Xavura
Copy link

Xavura commented Jul 12, 2012

Well... it's nice to know that exists but as @Nami-Doc said it's pointless with the current behaviour. Should it not compile to something like the following?

$ coffee -bce '({foo, doStuff} = {foo: "default", doStuff: false}) ->'

(function(_arg) {
  var doStuff, foo, _ref, _def;
  _def = {
    foo: "default",
    doStuff: false
  };
  if (_arg == null) {
    _arg = _def;
  }
  foo = (_ref = _arg.foo) != null ? _ref : _def.foo;
  doStuff = (_ref = _arg.doStuff) != null ? _ref : _def.doStuff;
});

@Xavura
Copy link

Xavura commented Jul 12, 2012

Of course, that's just how it should behave not necessarily how it should be implemented. 👅

Should it not compile to something like the following?

But yeah, as it stands, having to pass either none or all of the args is practically useless. If it's not going to be changed then the feature should be removed IMO.

@erisdev
Copy link

erisdev commented Jul 12, 2012

@Xavura well, no, it shouldn't be removed, either. Allowing default values for a destructured parameter isn't a special case; it's just a side effect of supporting destructured parameters and default values AFAIK.

@Xavura
Copy link

Xavura commented Jul 12, 2012

I don't know what made me think it would be a special case because it really makes no sense.

Well if that is so, then right you are! I should quit while I'm still ahead. 😪

@leeola
Copy link
Author

leeola commented Jul 25, 2012

Perhaps i skimmed too much, but is there a reason that the syntax @Xavura pointed out is somehow wrong? Sure, the current implementation shouldn't be touched outside of function definitions.. but i think adding in support for this is worth while.

With that said, i like the syntax @gkz pointed out much more.. very clean

@erisdev
Copy link

erisdev commented Jul 26, 2012

@leeolayvar because it doesn't compile to the suggested compilation; it compiles to this:

(function(_arg) {
  var doStuff, foo, _ref;
  _ref = _arg != null ? _arg : {
    foo: "default",
    doStuff: false
  }, foo = _ref.foo, doStuff = _ref.doStuff;
});

Why that doesn't work should be fairly obvious.

@leeola
Copy link
Author

leeola commented Jul 26, 2012

@leeolayvar because it doesn't compile to the suggested compilation; it compiles to this:

(function(_arg) {
  var doStuff, foo, _ref;
  _ref = _arg != null ? _arg : {
    foo: "default",
    doStuff: false
  }, foo = _ref.foo, doStuff = _ref.doStuff;
});

Why that doesn't work should be fairly obvious.

@erisdiscord Actually you just confused me more :/. Are you saying that the current implementation compiles to what you gave? Because if so.. i wasn't talking about currently, i was talking about that as if it were a proposed syntax.

One of the many options (see @gkz's option too, add = ({x = 4, y = 3}) ->) to handle keyword defaults.

Is there something that make these syntaxes inherently wrong for CoffeeScript? (Ie, do the proposed syntaxes break some other functionality of CoffeeScript, etc)

@epidemian
Copy link
Contributor

@gkz That's a cool LiveScript feature there! :)

One thing i don't like about that particular example...

add = ({x = 4, y = 3}) ->
  x + y

... is that you have to call add {} to get 7 instead of add(). This would be fixed by adding a default value to the object parameter:

add = ({x = 4, y = 3} = {}) ->
  x + y

But maybe it gets confusing with so many assignments in the parameters declaration hehe.

I'd say that one level of "defaultness" is probably enough. In this case, the = {} for the object parameter. Then you can use the ?= or or= operator as mentioned before:

add = ({x, y} = {}) ->
  x ?= 4
  y ?= 3
  x + y

@leeola
Copy link
Author

leeola commented Jul 27, 2012

@epidemian Why do that? Why not simply add in a default in the compiled JS? I think it would be safe to assume that the user wants the object to exist by default, if they want default values..

So why not compile this..

add = ({x ? 4, y ? 3}) ->
  x + y
alert add()

into something like..

var add;
add = function(__arg){
  var x, y, __ref;
  if (__arg == null) {
    __arg = {};
  }
  x = (__ref = __arg.x) != null ? __ref : 4, y = (__ref = __arg.y) != null ? __ref : 3;
  return x + y;
};
alert(add());

edit: Added the source LiveScript for clarity.

@leeola
Copy link
Author

leeola commented Jul 27, 2012

I mean, the rarer (in my opinion) case would be where the user wants default values for an object, but doesn't want the object to exist by default. And in theory, that could easily be done via..

add = ({x ? 4, y ? 3} = undefined) ->
  x + y
alert add()

@epidemian
Copy link
Contributor

@leeolayvar Well, i agree in that it would be convenient. But it would also add a special case for parameter default values semantics: "when you don't pass an argument to a function, it will be undefined unless it has an explicit default value in that function or unless the parameter is declared as an object destruct, in which case it will be an empty object {}". I'm not sure if that special case is so intuitive, or if it's a good trade-off to sacrifice consistency for convenience in this case.

@leeola
Copy link
Author

leeola commented Jul 27, 2012

I'm all for special cases when they make sense heh. Though, the bigger issue is simply getting default keyword arguments into CoffeeScript. The issue of whether or not default keyword values in functions should automatically define the object or not is a secondary, and far minor, issue.

From what i've seen, keyword default values have been raised multiple times, and keep being shot down.. so it's hard to say if this is nothing more than a pipe dream feature. Eventually talk will need to be started over what to do with CoffeeScript to keep it up to date when ES6 comes out, so perhaps this feature will be raised then..

@epidemian
Copy link
Contributor

@leeolayvar yeah, i think it's very difficult to implement keyword arguments in a language that does not support them natively. Although it's common practice to use an options object for keyword arguments in JavaScript and Ruby, i always felt it's kind of a hack. In languages that support keyword arguments natively (e.g. Python, Scala) using them feels like pure win :)

CoffeeScript can't do so much magic. It can provide a nice syntax alternative for the common case of passing an options argument; i think that having the ability to decalre default values in parameter destructuring like LiveScript has can be quite useful. But implementing keyword arguments support for all functions, even the ones that use normal positional arguments, while maintaining compatibility with JS is, i think, impossible.

The issue of whether or not default keyword values in functions should automatically define the object or not is a secondary, and far minor, issue.

Yeah, i agree it's not a major issue; but i think that defaulting to an empty object when using destructuring would make it less intuitive.. Destructuring can be used to implement something like keyword arguments, but it can also be used to destruct much more complex data structures. Consider the case:

showPlayerStats = ({name, league, matches: [
  {winner: {name: winnerName}, loser: {name: loserName}}
]}) ->
  won = name is winnerName
  opponentName = if won then loserName else winnerName
  $('.player-stats').html """
    <b>#{name}</b> (#{league} league)<br>
    Last match: #{if won then 'won' else 'lost'} vs #{opponentName}
  """

(sorry for the complex example... i was playing a game a it was the first thing that popped into my head)

The example is quite contrived, but the point is: should the matches array be [] if there's no matches property in the given object? Should the winner and loser objects be {} if the first match doesn't have those properties? I think they shouldn't. If the passed data structure doesn't have the needed properties and structure, i'd expect the function should fail. That's why i think default values should be explicit.

@leeola
Copy link
Author

leeola commented Jul 28, 2012

The example is quite contrived, but the point is: should the matches array be [] if there's no matches property in the given object? Should the winner and loser objects be {} if the first match doesn't have those properties? I think they shouldn't. If the passed data structure doesn't have the needed properties and structure, i'd expect the function should fail. That's why i think default values should be explicit.

First off, kudos for the interesting example lol. It took me a minute to even figure out what i would want from a function like that.

The best i could come up with.. is.. i wouldn't want that example to work. and i would expect the compiler to throw a type of Reference Error. name and league are fine, but matches defaults to a list, with an object in it.. that has no real data, correct? [{ winner: {name: winnerName }, loser: {name: loserName} }] is simply going to attempt to populate defaults for that object with winnerName and loserName. So.. with your given example, i wouldn't expect defaults of any sort, i would expect a reference error.

Now if those values do exist, as in the following example..

winnerName = "The team that wins by default"
loserName = "The team that loses by default"

showPlayerStats = ({name, league, matches: [
  {winner: {name: winnerName}, loser: {name: loserName}}
]}) ->
  won = name is winnerName
  opponentName = if won then loserName else winnerName
  $('.player-stats').html """
    <b>#{name}</b> (#{league} league)<br>
    Last match: #{if won then 'won' else 'lost'} vs #{opponentName}
  """

then i would expect matches to default to the following value:

{
  winner: {
    name: "The team that wins by default"
  },
  loser: {
    name: "The team that loses by default"
  }
}

Is my understanding of this wrong somehow? Perhaps i misunderstood the point

@leeola
Copy link
Author

leeola commented Jul 28, 2012

Perhaps your point was to showcase how cascading defaults for function arguments could spiral out of conttrol, and upon further examination i think i understand you.. i focused on the Reference error a bit too much hehe.

At any rate, defaults can only get you so far. Unfortunately, in the example of

showPlayerStats = ({name, league, matches: [
  {winner: {name: winnerName}, loser: {name: loserName}}
]}) ->

You have defined a default list with an object in it. I'm not sure how else you could even interpret that.. is this a common use case?

@epidemian
Copy link
Contributor

Perhaps your point was to showcase how cascading defaults for function arguments could spiral out of conttrol, and upon further examination i think i understand you..

Yeah, that's mostly what i wanted to point out; that given how powerful destructuring is, any change in its behaviour, no matter how simple it may seem, can have complex and unexpected ramifications.

You have defined a default list with an object in it. I'm not sure how else you could even interpret that..

The way to interpret this declaration:

showPlayerStats = ({name, league, matches: [
  {winner: {name: winnerName}, loser: {name: loserName}}
]}) ->

Is that showPlayerStats expects an object (a player, presumably =P) that has "name", "league" and "matches" properties (at least; it can have other properties too). The "matches" property has to be an array with at least one element that, in turn, has to be an object (a match) that has "winner" and "loser" properties. And those two "winner" and "loser" properties should be objects with a "name" property.

The variables that end up being in the function scope are "name", "league", "winnerName" and "loserName"; nothing else. There's no reference to the whole player, nor the "matches" array, nor the opponents. This is one of the biggest advantages of destruturing IMO: you only deal with the things you want to deal.

A complete usage example could be:

showPlayerStats = ({name, league, matches: [
  {winner: {name: winnerName}, loser: {name: loserName}}
]}) ->
  console.log """name: #{name}, league: #{league}, 
    last match winner: #{winnerName}, loser: #{loserName}"""

# Create two players with two matches.
jimmy = { name: 'Jimmy', league: 'Wood', matches: [] }
susan = { name: 'Susan', league: 'Copper', matches: [] }
match1 = { winner: susan, loser: jimmy }
match2 = { winner: jimmy, loser: susan }
player.matches.push match1, match2 for player in [jimmy, susan]

showPlayerStats jimmy 
# -> "name: Jimmy, league: Wood, last match winner: Susan, loser: Jimmy"

Well, i think i'm going off on a tangent here. The point it that destructuring can do much more than simulating keyword arguments; choosing a default behaviour that only makes sense for that case is not a good idea.

Now if those values do exist, as in the following example..
[code... ]
then i would expect matches to default to the following value:

Well, that's an interesting suggestion :). Though, if default arguments for object destructing are implemented as in LiveScript, using the = operator, i think it would make more sense to make more explicit what the default values are and write something like {name: winnerName = "No winner; both sucked"}.

is this a common use case?

Hehe. I have no idea. I sometimes use one or two levels of destructuring, but in "normal" assignment, not in function parameters (i haven't have the need to do it really... but i don't discard the possibility =P).

@leeola
Copy link
Author

leeola commented Jul 29, 2012

I would like to say that i sort of disagree with that example, as a whole lol.

I would support it as a default argument, but the example of making a deconstructed pattern work, i think is a going a bit too far. Too be honest, it is one of the more bizarre pieces of code i've seen lol.

showPlayerStats jimmy 
# -> "name: Jimmy, league: Wood, last match winner: Susan, loser: Jimmy"

This is mostly what i find wrong with this example. If you're using it as a default, that's fine, but if you're using it as some type of pattern, it's bizarre.. Why does winnerName and loserName default to the last match? Does the behavior of defining a pattern for matches: [{winner: {name: winnerName}, loser: {name: loserName}}] mean that the function pops the last element? It's confusing :s

So yea, while i think that is a mostly perfectly valid use of a default value, i do not agree with it in terms of a deconstruction example.. as it is either popping an element of the matches array, or referencing the last element.. both of which seem rather odd default behavior. You also mention that matches won't even be exposed in scope.. which i also find bizarre :s (edit: Bizarre.. because by deconstructing that, your function only has access to the last element)

I mean, if this is existing, working behavior in something then i guess fine, leave it alone lol, but i would not agree with this as a use case against defaults, as it feels like very bizarre behavior all around to me.

@epidemian
Copy link
Contributor

Too be honest, it is one of the more bizarre pieces of code i've seen lol.

Well, i said it was contrived :P. However:

Why does winnerName and loserName default to the last match?

It doesn't default to anything, it just takes the first element in the "matches" array (lets suppose that that is the last match for that dummy example). That's how destructuring an array works. A maybe more realistic example:

{abs, sqrt} = Math # <- Destructuring here too!
distance2d = ([x1, y1], [x2, y2]) ->
  sqrt (abs x1 - x2) + (abs y1 - y2)

# An array of 3D coordinates
coords = [[1,2,5], [5,5,5], [1,2,2]]

# Sort the 3D coordinates by their distance to a given target.
# distance2d doesn't care if the given coordinates are 2D or 3D, it just
# takes the first two elements.
target = [2,2,2]
coords = _.sortBy coords, (coord) -> distance2d coord, target
console.log JSON.stringify coords # -> [[1,2,5],[1,2,2],[5,5,5]]

Take a look at the documentation at coffeescript.org#destructuring to see more (and less bizarre...) examples. There is a "futurists" example there with complex destructuring.

@leeola
Copy link
Author

leeola commented Jul 30, 2012

It doesn't default to anything, it just takes the first element in the "matches" array (lets suppose that that is the last match for that dummy example). That's how destructuring an array works. A maybe more realistic example:

Well it defaults to the first element then, in my terminology heh. Though i was incorrect, i had read it incorrectly and thought it was defaulting to the last element, rather than the first.

Though, if i were to use code similar to that i would be very annoyed that the compiler would kill my reference to matches.. basically making the destructuring usage very limited like that.

At any rate, usage like that may be a thing of the past when ES6 hits.. but that's not for now hehe. To be honest, i think any discussion over defaults like this should be done in the frame of LiveScript vs CoffeeScript, as LiveScript already has a working implementation of defaults.. so i wonder if they just don't support destructuring as in your example?

Either way, i think this is mostly a dead conversation lol. Unless we get any real support, i don't think we'll see a feature pairing with CoffeeScript and LiveScript in regards to Keyword Defaults.. LiveScript wins this one heh

@epidemian
Copy link
Contributor

Though, if i were to use code similar to that i would be very annoyed that the compiler would kill my reference to matches.. basically making the destructuring usage very limited like that.

Well, that's kind of the point of destructuring: you don't care about the matches array, you only care about the first element.

A simple example:

users.forEach ({name}) -> console.log name

I don't care about the whole user object inside the function passed to forEach, i only care about its name, that's why i prefer destrcturing in this case instead of:

users.forEach (user) -> console.log user.name

At any rate, usage like that may be a thing of the past when ES6 hits.. but that's not for now hehe. To be honest, i think any discussion over defaults like this should be done in the frame of LiveScript vs CoffeeScript, as LiveScript already has a working implementation of defaults..

I think there are three different thing here:

  1. Default positional arguments.
  2. Default values for destructuring.
  3. That destructuring an object uses {} by default when null is given to it (what we were discussing... i think).

Both CoffeeScript and LiveScript support the first one; LS supports the second one; and neither CS nor LS support the third one.

Taken from the LS documentation:

setCords = ({x = 1, y = 3} = {}) -> "#x,#y"

You can see that, in order to being callable with no arguments, it must declare the default value for the the destructured object as = {}.

so i wonder if they just don't support destructuring as in your example?

Yes, it does, in conjunction with default values. This:

({foo = 42, bar: {baz = 'lol'}}) ->
  console.log foo, bar, baz

Compiles to:

(function(arg$){
  var ref$, foo, baz;
  foo = (ref$ = arg$.foo) != null ? ref$ : 42, baz = (ref$ = arg$.bar.baz) != null ? ref$ : 'lol';
  return console.log(foo, bar, baz);
});

@leeola
Copy link
Author

leeola commented Jul 30, 2012

Well, that's kind of the point of destructuring: you don't care about the matches array, you only care about the first element.

Fair

Yes, it does, in conjunction with default values. This:

({foo, bar: {baz = 42}}) ->
  console.log foo, bar, baz

Well yea.. but that's not really like the example you provided, wouldn't this be more akin to it?

f = ({foo, bars: [{bar: baz}] }) ->
  console.log foo, baz

f('foo', [{bar: 'baz'}])

At any rate, i was toying with LiveScript and i couldn't quite come up with a working example of defaults and destructuring.

f_normal = ({foo, bars: [{bar: baz}] }) ->
  console.log 'normal: ', foo, baz

f_default = ({foo, bars=[{bar: baz='default-baz'}] }) ->
  console.log 'default: ', foo, baz

f_normal foo:'foo', bars: [{bar: 'normal-baz'}]
f_default foo:'foo'
f_default foo:'foo', bars: [{bar: 'semi-default-baz'}]

# Outputs:
#normal:  foo normal-baz
#default:  foo default-baz
#default:  foo undefined

As you can see, it works in the first two, but not in the 3rd.. derstructuring fails there.. but it could easily be my implementation.

@epidemian
Copy link
Contributor

At any rate, i was toying with LiveScript and i couldn't quite come up with a working example of defaults and destructuring.

I don't know about those cases either. Notice that, in f_default you're assigning an array as the default value of bars; i don't think that array can work as a pattern for destructuring at the same time. Maybe @gkz can clarify this :)

@leeola
Copy link
Author

leeola commented Jul 31, 2012

I don't know about those cases either.

Well that was the point :s, i was trying to see if anything (LiveScript/etc) supports destructuring similar to the examples we've been discussing.

But for what it's worth, LiveScript tries to deconstruct it, as you can see below..

var f_normal, f_default;
f_normal = function(arg$){
  var foo, baz;
  foo = arg$.foo, baz = arg$.bars[0].bar;
  return console.log('normal: ', foo, baz);
};
f_default = function(arg$){
  var foo, ref$, baz, bars;
  foo = arg$.foo, bars = (ref$ = arg$.bars) != null
    ? ref$
    : [{
      bar: baz = 'default-baz'
    }];
  return console.log('default: ', foo, baz);
};
f_normal({
  foo: 'foo',
  bars: [{
    bar: 'normal-baz'
  }]
});
f_default({
  foo: 'foo'
});
f_default({
  foo: 'foo',
  bars: [{
    bar: 'semi-default-baz'
  }]
});

You can see that it exposes baz

@d4goxn
Copy link

d4goxn commented Feb 19, 2013

I know this is an old thread, but it is a high ranking search result for 'javascript keyword arguments'.

An outsider's perspective: In Python, all functions take their arguments as positional or as keywords. Any function can be called either way. For example:

def asyncDivide(a, b, ifDone, ifDivZero, ifNotANumber):
    try:
        result = a / b
        try: ifDone(result)
        except NameError: pass
        return result
    except ZeroDivisionError as error:
        try: ifDivZero(error)
        except NameError: raise error # The zero div error, not the name error.
    except TypeError as error:
        try: ifError(error)
        except NameError: raise error

def youFool(error = null):
    print('You fool!');

print asyncDivide(123, 0, onDivZero = youFool)

There are some restrictions, for example no keyword args can be specified before the positional args in the function call.

@jashkenas
Copy link
Owner

Skimming back through this massive thread, I think we're going to leave things as they are for now. Further cluttering up the parameter list is undesirable -- and I think we're best off keeping destructuring just destructuring, with no default values nested in the LHS of your match. Destructuring is already one of the most syntactically complex and confusing things you can do in the language, and sticking values in the match at least doubles the visual complexity.

FWIW, As far as I'm aware, ES6's destructuring won't support default values either.

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