-
Notifications
You must be signed in to change notification settings - Fork 2k
Optional function arguments (i.e. single-arg splats) #1091
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
Comments
It's worth noting that Node.js uses the style I've described above in its core API. See, for instance,
but the new syntax I'm proposing is both more succinct and, crucially, more self-documenting:
|
Since #870 didn't make it (bummer, I know), this one probably should not either. They kind of complement each other. |
I consider the two unrelated. #870 was proposing a couple of minor syntactic sugars: Being able to write |
Flagging as |
Would love this. With node.js there is |
I just started learning CoffeeScript and one of my first classes really could use this pattern. I found this page by googling for "coffeescript options hash" so I could learn the standard idiom for this. Trevor's outline seems well thought out and the syntax would have been something I expected to find currently implemented. |
Thanks, metaskills. There was an alternate proposal at #1225, which I think shows demand for this feature. It would be a fairly major change to the language, though. |
+1 Splats are awesome; this seems to me a natural complement. |
So for at least one of your examples:
It could be (actual notation not with standing):
As for the rest I don't really get it, it seems too complicated. But I'm not really fluent in coffee-script yet, so forgive my ignorance if I'm missing the point. |
@trans That feature was proposed (and rejected) at #1225. It's hard to see how it would interoperate with existing features like splats and default values, methinks. Whereas
is fairly clear: If you just pass one argument, it's used as |
Hmm... reading about #1225 and your explanation (which helped, thanks) could
Would tell it to get
If one argument is passed it is
Of course, pick whatever symbol would work best if |
I like/want this feature, but I'm concerned about confusion with the existing argument value defaulting behavior. Consider these two signatures: fn1 = (x, y = 5, z) ->
fn2 = (x, y ?= 5, z) ->
The syntax described above is especially confusing when you consider that in existing CoffeeScript code, the I would seem preferable to swap the meaning of these two, such that |
@brandombloom To clarify, this proposal is intended as a replacement for the existing argument value defaulting behavior. This would be a major breaking change, and I'd like it to be considered for CoffeeScript 2.0, which I hope will merit the rethinking of several features. |
Perhaps only the |
The only thing I see against this is that it won't remove type checking or proper variable detection. In the original example it's very easy to see the result of the operation. With the function header: |
@bbuck the original issue states that the leftmost argument has the highest precedence. 👍 for this, incidentally! |
@erisdiscord That still doesn't fix the issue that I presented. If the leftmost argument has precedence then with the given function: |
@bbuck Oh, duh, I misunderstood this part:
I suppose the onus would be on you, the programmer, to design an API that avoids those sorts of situations. |
Well, is it really a good idea to put in a language construct that you'll have to be consciously aware of when programming to avoid unwanted situations? While I'm not one for oversimplification of things, I also don't think that putting a construct like that in CoffeeScript, a language that is attempting to fix similar situations that JavaScript has with other things, would be the best path for the future. I don't oppose this feature, despite that I've only pointed out flaws. I just don't think this implementation is complete enough. |
+1 for splats |
Why not look at Python as a guide. It allows positional arguments to be followed by optional/default arguments and all of them can be set by name. If positional arguments are all referenced by name, or there are no positional arguments, then arguments can be set out of order: def create_task(name, needs_approval=False):
print name, needs_approval
# valid
create_task('coffee') # only set positional arg
create_task('coffee', True) # set default arg
create_task('coffee', needs_approval=True) # set default arg by name
create_task(name='coffee', True) # set positional arg by name but default by position
create_task(name='coffee', needs_approval=True) # set both args by name
create_task(needs_approval=True, name='coffee') # set out of order because all args referenced by name
# invalid
create_task() # no positional args
create_task(needs_approval=True, 'coffee') # breaks positioning since not all args are referenced by name Also, python supports splats for positional args in the form of def create_tasks(project, *args):
print project, args # args is a tuple aka immutable array
# valid
create_tasks('new_project')
create_tasks('new_project', 'task1')
create_tasks('new_project', 'task1', 'task2')
# etc. And default args in the form of def create_tasks(project, *args, **kwargs):
print name, *args, **kwargs
# valid
create_tasks('new_project')
create_tasks('new_project', 'task1')
create_tasks('new_project', 'task1', 'task2')
create_tasks('new_project', 'task1', deadline='tomorrow')
create_tasks('new_project', 'task1', 'task2', deadline='tomorrow', created_by='Joe')
# etc. It should be noted that The point is, positional args come first, then default args, then splats. |
Finally read through this, but not convinced. I think that the particular syntax here is fairly confusing -- particularly because of the reordering of the args. Currently:
Takes (maybe, this is JavaScript after all) two arguments, the first is Proposed:
Takes two arguments, or one, in which case the first arg is |
In cases where the first arguments are optional, you can get similar functionality by making arguments resolve to the right first, which can be easily done by simply padding the preceding (missing) arguments with nulls with an higher order function: rargs = (fn) -> (a...) ->
if (missing = fn.length - a.length) > 0
nulls = (null for [1..missing])
fn.apply this, [nulls..., a...]
else fn.apply this, a
# example
foo = rargs (a, b, c) -> { a, b, c }
foo 1, 2, 3 # -> a: 1, b: 2, c: 3
foo 1, 2 # -> a: null, b: 1, c: 2
foo 1 # -> a: null, b: null, c: 1
# works with default values, too
bar = rargs (a='a', b) -> { a, b }
bar 1 # -> a: 'a', b: 1
bar 1, 2 # -> a: 1, b: 2 |
I like this proposal, but I'd like to say that, regardless of syntax, being able to tell how to use a function just by looking at its definition is much more worth than the code it saves, for me. The problem with this proposal seems to be the syntax, not the idea itself. I have come up with another syntax. Some documentation use square brackets to denote optional arguments. What if we could adopt that, considering that many are already used to it? (Note: All my examples are examples of documentation, not function invocations! As far as I can tell, there are two variations: You either put commas inside or outside the square brackets.
Examples of "outside style":
Node:
Examples of "inside style":
PHP:
"Outside style" won't make it in CoffeeScript, though, since it already is used for destructuring. But "inside style" is currently a syntax error! PHP also brought up an interesting concept: "Nested optionality". To pass
If jQuery wanted to only allow data if a selector was passed, this could have been used:
For the other way around:
The nesting thing wasn't anything I considered initially. Is it too complex? As a downside, the syntax might look weird coupled with array destructuring:
And what about single-argument functions?
However, in that case a default value could simply have been provided:
So this would be backwards-compatible (I hope!), but might be confusing if you mix default values and optional arguments:
What does that mean? Well, it's equivalent to
or
or
I don't know about compilation and the finer details though ... Let's discuss ;) |
@jashkenas your example
wouldn't work in Python since So perhaps this alleviates some of your worry about confusing out of order args when calling functions? |
Short version
Splats soak up any number of values from 0 to infinity. As a result, you can only use one splat per argument list or array pattern. But there are cases where you want a single optional value, or more than one (potentially non-sequential). Implementing this every time is tedious and increases refactoring effort, making it a high-value language feature.
The proposed syntax is
or in array pattern-matching
Single optional arguments would take priority over splatted arguments, with leftward optional arguments taking priority over rightward ones. Optional arguments could be used in conjunction with default arguments, as in
Also, I suggest that the current
(arg = default) ->
implementation should be deprecated in favor of the more versatile(arg ?= default)
syntax.Uses in function arguments
Let's say that I have a function of the form
Having the
callback
as the last argument is good Node-y style, butoptions
should be, well,optional
. So I can either writeor
Neither is nearly as clean or self-documenting as
Let's take another example that uses an optional argument in conjunction with a splatted argument. I'm defining a function where I want at least one value to be in a splatted argument, whenever possible. I have a
setTimeout
wrapper that takes the formand I want
millis
to default to0
. So all of the following should be valid:Currently, implementing this API requires a rather annoying series of swaps:
It gets even worse as you have more arguments, since you have to "shift" each one.
The optional function argument syntax would allow the function to be implemented more readably (and with more efficient JavaScript output) with
(Note that this still doesn't take care of the case where a func and args are given but no millis, because this has to be addressed by checking the type of the first argument.)
Note that if you want to ensure that a splatted argument receives at least one value, you should make it a separate argument and concatenate them, e.g.:
Uses in pattern matching
Honestly, I can't think of any strong use cases in array pattern matching, but I believe the feature should be implemented nonetheless for consistency with splats.
Default values in array pattern matches were previously proposed at issue 810, but rejected as too complex. Unless anyone has a strong use case for it, I think the
?=
syntax should only be available for function arguments, not for pattern matching.Default values:
?=
vs.=
For the last argument(s) in a function, the
?=
syntax would be somewhat redundant with the existing=
syntax for default arguments. There is a subtle difference between them:?=
usesarguments.length
, while=
uses, er,?=
(confusing, right?), which means that the latter would override a givennull
orundefined
while the former would not.I think the best way to remove this confusion is to make
=
and?=
equivalent (the familiar=
syntax should probably be kept for the benefit of those coming from other languages), so that there's only one default argument behavior: one that doesn't override explicitnull
/undefined
. That is, bothand
should compile in such a way that if two arguments are given, the second is used as the callback.
Implementation details
We need a length check for each optional value. (I'm going for ease of implementation here. An optimized version would nest the conditionals so that fewer length checks would often be performed.) Assume that
arguments.length
is assigned to__len
. Here's what the existing splat syntax compiles to (breaking each assignment onto its own line):Now here's how the implementation would go for optional arguments, with the initial assignment of
_i = (index of first optional argument, ignoring splats)
:If a default value is given, substitute it for
void 0
. Obviously the expressions of the formx = void 0
can be stripped. (That postfix increment on the last_i
can also be stripped, but I've kept it here because it's in the current implementation.)The only changes needed to the splat implementation for it to interoperate would be 1) to use
_i
as theslice
start position if there are preceding optional arguments, and 2) to only change the value of_i
in such a case if the splat is non-empty. For all other purposes, optional arguments would work the same way as "required" arguments, since they take priority over splats. Examples:The size of the compiled output should give you some idea of how much effort this feature will save for CoffeeScripters who use it.
What do y'all think?
The text was updated successfully, but these errors were encountered: