-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Comments
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 |
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..
|
How would you implement this? |
How do we deal with situations like this? |
Could someone please give a small example of the compiled output for the On Mon, Jul 9, 2012 at 12:29 PM, bluepnume <
|
@leeolayvar
@mark-hahn it can't! |
I don't think we would..
"Which isn't that bad, honestly." well no.. but it's bad enough that default arguments were added into CoffeeScript.
@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..
and would compile to something like this..
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. |
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. |
@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. |
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.
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 :) |
…2427 - '=' sugar for '?'
I've added this feature to LiveScript (on master, haven't released a new version yet). Example:
Which compiles to:
|
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? |
It doesn't matter which order you put them in. |
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. |
already does what you want in LiveScript.
is just sugar for
|
Nice, I like how you implemented it. 👍 |
You should thank @satyr - I inherited these features from coco (the |
Already possible:
|
But this requires to pass either NO param or ALL params |
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?
(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;
}); |
Of course, that's just how it should behave not necessarily how it should be implemented. 👅
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. |
@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. |
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. 😪 |
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 |
@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, Is there something that make these syntaxes inherently wrong for CoffeeScript? (Ie, do the proposed syntaxes break some other functionality of CoffeeScript, etc) |
@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 = ({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 add = ({x, y} = {}) ->
x ?= 4
y ?= 3
x + y |
@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..
into something like..
edit: Added the source LiveScript for clarity. |
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..
|
@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 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.. |
@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 CoffeeScript can't do so much magic. It can provide a nice syntax alternative for the common case of passing an
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 |
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. Now if those values do exist, as in the following example..
then i would expect
Is my understanding of this wrong somehow? Perhaps i misunderstood the point |
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
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? |
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.
The way to interpret this declaration: showPlayerStats = ({name, league, matches: [
{winner: {name: winnerName}, loser: {name: loserName}}
]}) -> Is that 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.
Well, that's an interesting suggestion :). Though, if default arguments for object destructing are implemented as in LiveScript, using the
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). |
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.
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 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 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. |
Well, i said it was contrived :P. However:
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. |
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 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 |
Well, that's kind of the point of destructuring: you don't care about the A simple example: users.forEach ({name}) -> console.log name I don't care about the whole user object inside the function passed to users.forEach (user) -> console.log user.name
I think there are three different thing here:
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
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);
}); |
Fair
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. |
I don't know about those cases either. Notice that, in |
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 |
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. |
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. |
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 usefoo = (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?
The text was updated successfully, but these errors were encountered: