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

Add postfix return statements #2729

Closed
wants to merge 4 commits into from
Closed

Add postfix return statements #2729

wants to merge 4 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Feb 27, 2013

There are a bunch of different ways to tunnel errors through asynchronous functions.

# You could use an if clause and indent your code.
# This is ugly, and inflates your indentation level.
processFile = (file, cb) ->
  fs.readFile file, 'utf8', (err, data) ->
    if err?
      cb err
    else
      cb undefined, doSomething(data)

# You could add a return statement to the end of your if clause.
# This can be hard to read, and still isn't a one-liner.
processFile = (file, cb) ->
  fs.readFile file, 'utf8', (err, data) ->
    if err?
      cb err
      return

    cb undefined, doSomething(data)

# Or, you could return the call to the callback.
# This, though, leaks the callback return value,
# and is kind of an abuse.
processFile = (file, cb) ->
  fs.readFile file, 'utf8', (err, data) ->
    return cb(err) if err?
    cb undefined, doSomething(data)

# You could use parentheses to combine the callback call
# and return statement.
# This requires the usage of semi-colons, and an extra set
# of parentheses.
processFile = (file, cb) ->
  fs.readFile file, 'utf8', (err, data) ->
    (cb err; return) if err?
    cb undefined, doSomething(data)

I propose adding a postfix return statement - that is, adding then return to the end of an expression would execute the expression and then return nothing.

So, this:

processFile = (file, cb) ->
  fs.readFile file, 'utf8', (err, data) ->
    cb err then return if err?
    cb undefined, doSomething(data)

Would compile to this:

(function() {
  var processFile;

  processFile = function(file, cb) {
    return fs.readFile(file, 'utf8', function(err, data) {
      if (err != null) {
        cb(err);
        return;
      }
      return cb(void 0, doSomething(data));
    });
  };

}).call(this);

This doesn't break any existing functionality, as <expression> and return is already a parse error.

A more complex example:

readFiles = (directory, cb) ->
  fs.readdir directory, (err, files) ->
    cb err then return if err?
    output = {}
    for file in files then do (file) ->
      fs.readFile path.join(directory, file), 'utf8', (err, data) ->
        cb err then return if err?
        output[file] = data
        files.pop()
        cb undefined, output if files.length is 0

In the attached implementation (which includes tests), and return, appended to a statement, is rewritten to a POST_RETURN token (the rewriter watches for a LOGIC/&& token followed by a RETURN/return token). This then triggers a special case on the Return node.

Thoughts?

They work like this:

    readFiles = (directory, cb) ->
      fs.readdir directory, (err, files) ->
        cb err and return if err?
        output = {}
        for file in files then do (file) ->
          fs.readFile path.join(directory, file), 'utf8', (err, data) ->
            cb err and return if err?
            output[file] = data
            files.pop()
            cb undefined, output if files.length is 0

`and return`, appended to a statement, parses to a POST_RETURN token,
which triggers a special case on the Return node. The token is generated
through the rewriter, by watching for and tagging any combination of
`LOGIC/&&` followed by `RETURN/return`.
Take the start column and line of the `and` token and move them over to
the resulting single `POST_RETURN` token.
@epidemian
Copy link
Contributor

While the purpose is noble, i don't like the usage of and for this. and is a boolean operator with short circuiting and is quite often used to express conditional execution (mostly in JS; no so much in CS, as it has the post if form):

validateMove() and makeMove()

This proposal would make the right-hand side of the and (return) to always execute irregardless of the result of the left-hand side, which i think is counterintuitive.

Maybe another keyword could be used. This is also a compiler error as of now an also reads quite nicely:

cb err then return if err?

I personally don't see that much value in adding this... but we shall see.

@mehcode
Copy link

mehcode commented Feb 27, 2013

Agreed that I don't see value in this.

However I would prefer <expression> then return [<expression>] as well.

@ghost
Copy link
Author

ghost commented Feb 27, 2013

@epidemian You're right! then return would be a much clearer choice. Updated.

@michaelficarra
Copy link
Collaborator

I don't like overloading the then keyword. then introduces a block on a single line. I think it should stay that way.

@ghost
Copy link
Author

ghost commented Feb 27, 2013

What about dropping the "linking" keyword entirely, then, and having "return" as a simple postfix operator like if?

cb err return if err?

@davidchambers
Copy link
Contributor

A higher-order function can provide much of the benefit of the proposed syntax addition:

handleError = (errorHandler) -> (success) -> (err, rest...) ->
  if err then errorHandler err else success rest...

With handleError:

$$ = handleError callback
Fodo.roots $$ (roots) ->
  root = _.find roots, predicate
  Fodo.children [root], $$ (children) ->
    # ...

Without:

Fodo.roots (err, roots) ->
  if err
    callback err
    return
  root = _.find roots, predicate
  Fodo.children [root], (err, children) ->
    if err
      callback err
      return
    # ...

@jashkenas
Copy link
Owner

Yep -- I don't think we should expand the meaning of then just for this. Indenting for these cases really ain't so bad:

if err?
  cb err
  return

In an unrelated thought -- is there any good reason why return isn't an expression (at least syntactically)? Other than that you can't actually use it as one? func(thing) && return seems like it would be useful in JavaScript.

@jashkenas jashkenas closed this Feb 28, 2013
@ghost ghost deleted the and-return branch February 28, 2013 01:37
@epidemian
Copy link
Contributor

@jashkenas,

In an unrelated thought -- is there any good reason why return isn't an expression (at least syntactically)?

Is there any distinction between "statement expression" and "value expression"? I could see a statement like func(thing) and return something, where return would act as a statement, making sense and being useful; but something like val = return something or func(return 42), where return acts as a value, wouldn't make much sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants