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 values for function arguments #2201

Closed
extempl opened this issue Mar 19, 2012 · 8 comments
Closed

Default values for function arguments #2201

extempl opened this issue Mar 19, 2012 · 8 comments
Labels

Comments

@extempl
Copy link

extempl commented Mar 19, 2012

Why do you use if (liquid == null) liquid = "coffee"; instead of if (liquid === undefined) liquid = "coffee";?
Because in your case I can't pass the null argument for replace the dafault non-null argument.

@michaelficarra
Copy link
Collaborator

See #802, the issue that added default values for arguments. Please search next time.

@matthewwithanm
Copy link
Contributor

Sorry to be a pest, but I'm looking for the rationale for this too. I seem to remember some discussion about it on Twitter between @jashkenas and somebody (Eich?) but I can't find it now, and #802 doesn't seem to contain it. (Actually, it kind of reads like everybody coming to the consensus of using arguments.length but then the change is merged and discussion stops.) I've also searched the mailing list without luck, so a point in the right direction would be super appreciated.

Thanks!

@jashkenas
Copy link
Owner

The rationale is that the unfortunate distinction between null and undefined in JavaScript shouldn't be abused. CoffeeScript treats both as a way to say "no value". You can opt-in to a function's default value by explicitly passing null.

@matthewwithanm
Copy link
Contributor

Thanks @jashkenas. I completely agree about null/undefined, but disagree that it's relevant to the case of default arguments. After all, neither Ruby nor Python suffer from the "multiple no values" issue, but both are consistent in favoring explicitly passed nil/Nones over default arguments. That unprovided JavaScript arguments happen to have the value undefined just seems like an unimportant side-effect of their omission. Metaphorically speaking, it's the difference between somebody not telling you their name, and somebody telling you they have no name.

In any case, I appreciate that this is "settled law" in CS, and that, given that, these kinds of questions can be very tiresome. So thanks for taking the time to answer—and for doing it so quickly! It's much appreciated.

@satyr
Copy link
Collaborator

satyr commented Jul 1, 2012

Coffee's default arguments, being simply a sugar for a version of the JS idiom function (a) { a || (a = defaultValue) }, is fundamentally different than Python's and Ruby's which care about the number of arguments passed.

@matthewwithanm
Copy link
Contributor

Yes, but the goal is clearly not to duplicate the behavior of the idiom exactly (or else other falsy values, like zero, would also be replaced by the default), but rather to provide the functionality that people are attempting to recreate when using the idiom. The idiom itself is a least-bad compromise we make to emulate default arguments—mostly because of its length in comparison to more unweildy solutions that would give us the exact behavior we desire (e.g. handle falsy values correctly). I understand that CoffeeScript's approach is fundamentally different than Ruby and Python's; my question was really about why the decision was made to make it so fundamentally different.

Ultimately, I think, the answer boils down to which of the following you feel is more important:

  1. The ability to opt in to the default regardless of argument position (without adding additional syntax), or
  2. The ability to have default values for nullable arguments.

Ruby and Python, at least, both take the position that 2 is more important and 1 is, in fact, not important at all (as only trailing arguments take the defaults). Philosophically, this seems like a sensible position as it seems important that a language (programming or otherwise) be able to convey both the idea of not having a favorite color (for example) and not specifying a favorite color, distinctly.

The design of CoffeeScript, on the other hand, places more importance on 1.

Of course, both 1 and 2 could be attained by using a sentinel value, but that's what undefined is, and we're trying to avoid that "multiple no value" mess, so that's a nonstarter—and rightfully so!

Both 1 and 2 could also be (nearly) attained, I assume, by adding syntax to the language (which is why I include the caveat); something along the lines of f(1, 2, , 4) or f(1, 2, -, 4) or f 1, 2, --, 4 etc., which would allow the user to opt-in to the default without creating an additional "no value" value (or even exposing the existence of a second no-value (undefined) in JavaScript to the unsuspecting CoffeeScripter).

Again, I do understand that this is an old decision too ingrained to be revisited this late in the game, and I want you to know that I'm not campaigning for a change or anything (which I realize this may sound like). I just wanted to understand the reasoning behind this design decision. I think I do now insofar as you guys felt that the ability to have default values for nullable arguments wasn't worth losing default arguments in non-tail positions or the cost of additional syntax.

Thanks again @jashkenas and @satyr for jumping in on this thread. It's been really interesting, and I'm sure I won't be the only one curious about the subject!

@satyr
Copy link
Collaborator

satyr commented Jul 1, 2012

(as only trailing arguments take the defaults)

Ruby 1.9+ allows non-trailing defaults:

def f a=0, b; p [a, b] end
f 1, 2  #=> [1, 2]
f 1     #=> [0, 1]
f       #=> ArgumentError: wrong number of arguments (0 for 1)

It's technically possible to implement positional default arguments (proposed at #1091), but the compilation would be more complex and non-idiomatic (non-goals for Coffee).

@matthewwithanm
Copy link
Contributor

Ruby 1.9+ allows non-trailing defaults

Ah, cool. Regardless, I think it just underscores my point—both because of its late addition and because it's still impossible to opt in to an arbitrary default argument, e.g. b in def f a=0, b=1, c=2 when explicitly passing a and c, in contrast to Coffee's use of null.

It's technically possible to implement positional default arguments (proposed at #1091), but the compilation would be more complex and non-idiomatic (non-goals for Coffee).

From my point of view, CS already has this ability—it's just implemented in a way that prohibits defaults for nullable arguments. If the goal were to allow both arbitrarily positioned defaults (including in non-beginning and non-trailing positions) and to honor explicitly passed nulls, checking against undefined seems the obvious route and clearly idiomatic to JS. However, like I said above, creating a distinction between null and undefined in CS is also a non-goal which means that, while the compiled check would be implemented using undefined, an an explicitly ommitted center argument would need to be indicated in Coffee with a new syntax. In other words,

f = (a = 0, b = 1, c = 2) ->
    console.log [a, b, c]

f 9, -, 9

becomes

var f;

f = function(a, b, c) {
  if (a === undefined)
    a = 0;
  if (b === undefined)
    b = 1;
  if (c === undefined)
    c = 2;

  return console.log([a, b, c]);
};

f(9, undefined, 9);

I'm not proposing a specific syntax here, just showing how both goals could be achieved by the addition of some new syntax. Of course this has its own drawbacks: using syntax instead of an identifier means that conditional omission would be difficult and, knowing the implementation, people would probably just resort to using undefined:

f = (a = 0, b = 1, c = 2) ->
    console.log [a, b, c]

if SOMETHING
    f 9, 9, 9
else
    f 9, -, 9

# Not as short as this:
f 9, (if SOMETHING then 9 else undefined), 9

This is @jashkenas's objection; that, because the underlying implmentation would recognize a difference between null and undefined, CS would essentially be adopting that distinction. Again, this issue only comes into play if one values non-tail defaults over the ability to explicitly pass null; otherwise, arguments.length could be used. Ruby and Python seem to have made the opposite calculation: that the ability to explicitly pass nil/None is more important than the ability to opt in to arbitrarily positioned defaults.

Gasol added a commit to Gasol/aglio that referenced this issue May 13, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to danielgtaylor/aglio that referenced this issue May 13, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 13, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 13, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 13, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 14, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 14, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Gasol added a commit to Gasol/aglio that referenced this issue May 15, 2019
Use NPM scripts to run coffeelint

- Update coffeelint to 2.1.0

  Also update Coffeescript and explictly depends on version 2,
  Use `undefined` as default argument instead of null, it's breaking
  changes on Coffescript 2.

  See:
    - https://coffeescript.org/#breaking-changes-default-values
    - jashkenas/coffeescript#2201

- Remove grunt and its plugins

  inlcude grunt-coffeelint, grunt-contrib-coffee and grunt-mocha-cov,
  Remove Gruntfile.coffee by using NPM scripts, and remove path of "npm bin"
  in scripts, Because the command of "npm run" adds node_modules/.bin to the
  PATH provided to scripts automatically.

- Update vulnerable dependencies

  include socket.io, chokidar

- Explictly depends on mocha

  Add --exit flag

  "By default, Mocha will no longer force the process to exit once all
  tests complete. This means any test code (or code under test) which
  would normally prevent node from exiting will do so when run in Mocha.
  Supply the --exit flag to revert to pre-v4.0.0 behavior"

  mochajs/mocha#2879
  https://mochajs.org/#-exit

- Use c8 to output coverage instead of blanket
- Add generate-examples scripts

  To replace 'gen-examples' task defined in Gruntfile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants