jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.49k stars 1.99k forks source link

Proposal: Optional (pre-last) function arguments #5318

Open celalo opened 4 years ago

celalo commented 4 years ago

It has been submitted before #1091, #4148 but it has been a while since last discussed so I'd like bring it forward again. I've been an avid coffeescript user since the early days and I missing this feature all the time.

instead of:

fn = (optional_arg1, optional_arg2, cb) ->
  unless cb?
    unless optional_arg2?
      cb = optional_arg1
    else
      cb = optional_arg2

wouldn't it make it sense to have it as:

fn = (optional_arg1?, optional_arg2?, cb) ->

or better yet, decorating optional arguments with default values:

fn = (optional_arg1 ?= true, optional_arg2 ?= false, cb) ->

for example:

retriveRecords = (perPage ?= 20, page ?= 1, cb) ->

above function can be used neatly as:

retriveRecords page, cb for page in [1...20]

or as:

retriveRecords -> #callback result with arguments page=20 & page=1

my workaround at the moment is like this:

retriveRecords = ({perPage, page}, cb) ->
  perPage ?= 20
  page ?= 1

retriveRecords perPage: 50, ->
retriveRecords page: 5, ->
retriveRecords perPage: 50, page: 5, ->
retriveRecords {}, ->
GeoffreyBooth commented 4 years ago

Can you please include what the expected output JavaScript would be for your examples?

Also are there any ECMAScript proposals at any stage of the TC39 approval pipeline that overlap with this?

celalo commented 4 years ago

Can you please include what the expected output JavaScript would be for your examples?

fn = (opt?, cb) -> do cb

could compile into

fn = function(opt, cb) {
  if (typeof cb === "undefined") {
    return [opt, cb] = [undefined, opt];
  }
  return cb();
};

fn = (opt ?= 42, cb) -> do cb

could compile into

fn = function(opt, cb) {
  if (typeof cb === "undefined") {
    return [opt, cb] = [42, opt];
  }
  return cb();
};

retriveRecords = (perPage ?= 20, page ?= 1, cb) ->

could compile into

retriveRecords = function(perPage, page, cb) {
  if (typeof cb === "undefined" && typeof page === "undefined") {
    return [perPage, page, cb] = [20, 1, perPage];
  } else (typeof cb === "undefined") {
    return [perPage, page, cb] = [perPage, 1, page];
  }
};

Also are there any ECMAScript proposals at any stage of the TC39 approval pipeline that overlap with this?

I could not find anything related.

GeoffreyBooth commented 4 years ago

So I think you would need to compile into == null rather than === undefined to match the output of ? in other contexts (i.e. null or undefined, not just undefined). Though there's the complication that since CoffeeScript 2 default parameters are only applied for undefined, to match ES6, so arguably ? in the function parameter context should follow this undefined-only rule.

This also looks challenging to implement. Keep in mind it would need to apply also for => functions and class/object methods. Are you up for taking this on?

One more consideration is simply, how useful is this? Node uses the style in its callbacks of putting err first specifically to save people the hassle of having to parse parameters like this. The object style, e.g. fn(options), has also become increasingly popular especially with destructuring (fn = ({ foo, bar }) ->). It seems less common nowadays to see functions with lots of arguments, especially optional ones in the non-final position.

Inve1951 commented 4 years ago

Here's a tidy general-purpose implementation: #try

retriveRecords = (perPage, page, cb) ->
  defaults = [20, 1]
  [perPage, page, cb] = [
    defaults[0 ... retriveRecords.length - arguments.length]...
    arguments...
  ]

The signature length would probably need to be hardcoded at compile-time, and for fat arrow functions the parameter list would need an arguments replacement like soaking args.... defaults could also be inlined to not clutter the scope.

CoffeeScript (and JS for that matter) typically has callback arguments as the last function parameter to allow inline function definitions at the call site. It's true that this creates the hassle of having to rearrange the arguments if some of them are optional.

I too found this to be a common pattern/burden in my projects and am all for getting this language feature.

I'm uncertain of the proposed ?= syntax however. I'd expect ?= to give default arguments back their CoffeeScript v1 meaning.