Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Syntax for indicating that a function should not return a value #899

Closed
TrevorBurnham opened this Issue · 73 comments

24 participants

Trevor Burnham Jeremy Ashkenas Stephen Moore s.ross Satoshi Murakami Casey O'Hara Michael Ficarra Aseem Kishore Mason Marco Aurélio 7rans László Bácsi Andras Tarsoly Alex Wayne David Chambers Ryan Scott Lewis dimatter edvakf Drew Pirrone-Brusse Eirik Albrigtsen Simon Greenwold Matias Meno Nijiko Yonskai Nick Retallack
Trevor Burnham
Collaborator

This is a fork from issue 896. To review: It's common to write a function that performs a loop at the end but where the list generated by that loop should not be returned. The best current solution is to add return to the end of the function, but this feels a bit kludgy, especially in one-liners. For instance:

showInts = (arr) -> console.log x for x in arr when isInt(x); return

Failing to do this is a common pitfall not just for CoffeeScript newcomers, but even for pros. Look at underscore.coffee, where _.mixin and _.times both generate lists, making them far less efficient than their underscore.js brethren. I think this speaks to how unintuitive the trailing return is.

So, I propose that putting void in front of -> or => should tell the compiler: "This function is not supposed to return a value. Don't generate returns implicitly. And raise an error if return anything explicitly." Then the example above would become

showInts = (arr) void -> console.log x for x in arr when isInt(x)

And underscore.coffee wouldn't have to grow 2 lines longer in order to fix _.mixin and _.times:

_.mixin = (obj) void ->
  for name in _.functions(obj)
    addToWrapper name, _[name] = obj[name]

The void -> syntax is not only more succinct, but also more self-documenting than the return syntax. If I'm relatively new to CoffeeScript and I see a trailing return, I'm baffled—what is that doing there? Whereas if I see void, a keyword that isn't allowed in any other context in CoffeeScript, I can look it up immediately in the documentation.

Thoughts?

[On further reflection: Instead of void, how about *->? For instance, compare

readFromDatabase (result) *-> console.log result

to the current

readFromDatabase (result) -> console.log result; return

A pretty significant readability improvement, don't you think?]

Jeremy Ashkenas
Owner

Nope, Trevor, sorry ... We don't need to introduce a new keyword to accomplish something you can already be doing by adding a null to the end of the function. I apologize for _.mixin and _.times, my only excuse is that I was doing a direct JavaScript port.

CoffeeScript forces you to adopt the habit that you have to think about having a good return value for every function you write -- and I think it's a great habit to have. There's a lot of JS out there that would benefit from better return values, even for side-effect-ful code. Making it easier to create value-less functions is the opposite direction.

Stephen Moore

Can we at least use such a syntax to specify a default return value in the signature?
(i.e. true, 0, 1, null, undefined, {}, [1,2], etc)

So that if return isn't used before the function is ended, then that default value is used.

This would be very helpful for functions that creep to the right quite a bit due to many nested functions, where you still want to return some value at the bottom. Currently, you end up created a triangle

someFunction () ->
    anotherFunction ->
        # do some things
        # and some more things
        # etc

        anotherGetter ->
            # and some more logic
            # logic is good for the brain
            # stuff

            moreNesting ->
                # Sometimes it's necessary
                # for this amount of nesting
                # so far we've created a diagonal line
                # Yay for no closing braces !

    # and we still need to return true
    # and we've created a triangle
    true

Possibly even take some inspiration from google go and be able to define in the signature the variable in the body of the function that should be returned......

:)

Trevor Burnham
Collaborator

I keep thinking back on this issue as I work, and I'd like to see it reopened.

I agree with the philosophy behind implicit return values, and I certainly want to keep them. But the current situation forces CoffeeScript coders unnecessarily to choose between writing clean-looking code and truly efficient code. This isn't an issue in Ruby, partly because bytecode size isn't an issue in Ruby, but mainly because Ruby doesn't have implicit list comprehensions.

For instance, the following code looks elegant, but is in fact extremely inefficient—in terms of byte code, performance, and most of all memory—since it creates and returns a deep copy of canvas:

applyPixelFilter = (canvas, pixelFilter) ->
  for x in [0...canvasWidth]
    for y in [0...canvasHeight]
      canvas[x][y] = pixelFilter canvas[x][y]

Granted, adding a return here isn't too big a deal. But as delfick says, the aesthetic problem gets worse as you nest functions; and especially in asynchronous code, it's very desirable for functions to not return anything.

I don't think this has to do with whether programmers have to think about what functions return or not. Whether there's a special syntax for it or not, we're returning the last expression value from a function unless the programmer specifies otherwise.

I think delfick's idea of being able to specify a default return value for a function (that is, something that's returned unless the return keyword is used) is an excellent generalization of my proposal. Perhaps the syntax I suggested isn't ideal, but something that lets a return value (limited to, say, true, false and void) be declared at the top rather than at the bottom would be a huge aesthetic win. Using the reserved default keyword to call attention to it might work.

Trevor Burnham
Collaborator

Here's another proposal: Let *-> and *=> indicate functions that don't return values. Compare

_.times = (n, iterator, context) ->
  iterator.call context, i for i in [0...n]
  return

to

_.times = (n, iterator, context) *->
  iterator.call context, i for i in [0...n]

I find the latter both more writable and more readable. I keep hearing complaints about the need to give explicit returns, even in anonymous functions, to prevent excess code from being generated. This syntax would, in my view, be a major improvement, consistent with the core philosophy of the language.

Trevor Burnham
Collaborator

There's been some recent discussion about this on the Google Group.

As I've continued to work in CoffeeScript, I've developed a habit of adding return at the end of nearly every callback to prevent unwanted side effects. Not every idea I've had for improving this situation still seems wise to me, but having *-> and *=> to declare functions as "side-effects only" does. I hope it'll be reconsidered.

Jeremy Ashkenas jashkenas reopened this
s.ross

Continuing with Trevor's comment above, I believe that the additional clarity lent the code written with a *-> or *=> operator is desirable. As a Rubyist, I expect methods to return the last evaluated value. That is not always common Javascript expectation and Coffeescript is, after all, "only Javascript", hence should enable common usage patterns when possible.

As a further note, clarity has the benefit of making the code easier to read and less error-prone during a refactor. One cost associated with a language that is whitespace-sensitive is that lining up scopes can be, at times, daunting. Sprinking in explicit returns just for the sake of returning nothing can introduce errors and/or make refactoring difficult.

Satoshi Murakami
Collaborator

The look of *-> is puzzling. void seems clearer to me.

s.ross

The syntactic sugar, IMO is secondary to the actual decision to add an operator. However, if it does get down to syntax, I agree that the asterisk connotes less than the word void about the expected return value.

Trevor Burnham
Collaborator

Sure, the initially proposed void -> syntax would also be acceptable. That would, at least, give the syntax a name. If you tried to write something like

sideEffectsOnly = void ->
  console.log 'This function is only supposed to produce side effects'
  return false

then the compiler could give you the error void functions cannot return a value.

Casey O'Hara

I agree with satyr. While void is certainly more keystrokes than *, it is less than return. It's more succinct than both.

Satoshi Murakami
Collaborator

console.log 'This function has no side effects'

Self-contradiction? (Logging is a side-effect.)

Trevor Burnham
Collaborator

Oops, typo. Corrected, thanks.

Not sure if there's a good term for such functions. "Anti-pure functions"? "Void functions" works well for our purposes.

Michael Ficarra
Collaborator

@TrevorBurnham: Why would void have to apply only to functions? We could just introduce the JS void operator which would allow us to write expressions like void inner_expr that produce an undefined value. Also, a lower precedence than in JS would be nice to allow void (1 + 2) to be unparenthesized.

Trevor Burnham
Collaborator

@michaelficarra Hmm. But then void would have two very different meanings in CoffeeScript: one for functions, one for everything else. Is void inner_expr really a useful idiom in CoffeeScript?

Michael Ficarra
Collaborator

@TrevorBurnham: The meanings would be the same. It is the same operator in both places. Can you outline any differences?

$('something').click -> void do =>
  'do something'

and with arguments:

$('something').click (x, ...) -> void do (x, ...) =>
  'do something'

I still don't even think this is an issue, but at least the void operator is a better solution than another funky arrow glyph (or two!). Appending a return to explicitly specify a return value (even an undefined one) makes sense. When you don't care about the return value, just forget about it.

Trevor Burnham
Collaborator

@michaelficarra

$('something').click (x, ...) -> void do (x, ...) ->
  'do something'

is very different from the proposed

$('something').click (x, ...) void ->
  'do something'

In addition to DRY concerns, the first approach discards this (unless => is used on the inner function), and adds the overhead of an extra function call each time the callback is invoked. Would you really use that technique?

Clarification: The proposed compilation of the given example is

$('something').click(function(x, ...) {
  'do something';
});

void just removes the return.

As to "When you don't care about the return value, just forget about it.": I started adding return to callbacks as a matter of habit not because I'm nitpicky or efficiency-obsessed, but because it was becoming a major source of bugs. I wasted a lot of time before I realized that Vows interprets any return value from a callback other than undefined as meaningful. I got tired of having to look through documentation or source code of every new library I tried to check whether the return value on callbacks was significant. And as folks on the Google Group have pointed out, the abundance of accidental returns hurts the readability of JS output.

Michael Ficarra
Collaborator

@TrevorBurnham:

Whoops, forgot =>. Fixed. And no, I wouldn't use that technique because (as I described in the final paragraph) if I want to specify a return value other than that of the last expression, I will use an explicit return statement. In my opinion, that should be the one and only way to specify a return value. I believe it was you who taught me the python philosophy "TOOWTDI". I was just putting forward this void operator idea as a slightly more versatile way to give you a syntactic construct that will cause a function to produce undefined.

edit in response to your edit: The compilation can be simplified down when that pattern is recognized, but that's besides the point. Your argument regarding libraries whose behaviour is dependent upon the return value of your function is not very convincing. Either you care about the return value or you do not. And some of those times when you do, that return value is not the value of the last expression of the function body. In libraries like the one you mentioned, you do care about the return value and you do want it to to be different than the last expression. That's the only time the return keyword is used at the end of a function body. And you're suggesting we specify at the function head (through use of an alternative arrow syntax) that we would like to emulate the effects of a return without any expression being tacked onto the body? That just seems nowhere near necessary to me. And definitely not TOOWTDI. More like Perl's "There's An Unreasonably Large Number Of Ways To Do It" (tm).

Trevor Burnham
Collaborator

@MichaelFicarra I suppose that adding void as you propose it would at least solve the one-liner problem elegantly. To rewrite my original example, instead of writing

showInts = (arr) -> console.log x for x in arr when isInt(x); return

you could write

showInts = (arr) -> void console.log x for x in arr when isInt(x)

and the compiler, seeing a void expression on the last line, would compile the function with no list comprehension and no return. It'd avoid the visual awkwardness of the glyph, and the potential issue of having to look at the start of a function to see whether it returns a value or not.

Note that I'm assuming precedence ordering such that the above would be equivalent to

showInts = (arr) -> void (console.log x for x in arr when isInt(x))

rather than the less helpful

showInts = (arr) -> (void console.log x) for x in arr when isInt(x)

So, I'm warming to your suggestion. It feels like a good compromise. Essentially, void would function as the anti-return: "Don't return this expression." I'll happily take

void expression

over

expression; undefined
Aseem Kishore

After reading this whole thread, I really have to +1 Trevor's points and the proposal for the void operator.

Personally though, I'll chime in that I'm not a huge fan of automatic/implicit return values for multi-line functions. I get the beauty of not having to write "return foo" for one-liner functions -- just like Python's lambdas -- but in multi-line functions, it's just not obvious when you're reading a function and you don't see the word "return" that a value is getting returned. Because of that, I've tended to generally use the keyword "return" for returning values in non-trivial functions even though I don't need to. And yes, I've had to explicitly return nothing at the end of functions when I mean a function to be void.

Mason

I'd also like to convey my +1 for Trevor's proposal ('void' or '*' or whatever the final syntax is). The main reason is that this makes the code more self-documenting.

I really appreciate the implicit return feature of CS. But there will continue to be many cases where return values are explicitly unwanted. In those cases, having the first line of the function definition unambiguously tell you 'this function does not return a value' is very useful. (Even more so when you use an editor that can collapse functions--otherwise you have to uncollapse the function and inspect its code to see whether it has a meaningful return value.)

Marco Aurélio

Just tripped over a performance issue caused by the implicit return of a list comprehension today, so +1 for this proposal.

I would only like to suggest the following shorthand for -> void, instead of the already proposed *->:

-/>

I think it looks pretty cool, and it kind of gives the impression that the flow of information is somehow "cut" or "forbidden"

f = (list) -/>
  for item in list
    console.log item

It's only a shorthand though, so it would work the same as using -> void.

Bound functions could then use the similar =/> shorthand.

Trevor Burnham
Collaborator
7rans

Do I understand correctly? The proposal is for this:

f = (list) -/>
  for item in list
    console.log item

instead of already valid:

f = (list) ->
  for item in list
    console.log item
  null
7rans

Note in Ruby returning method was invented, but it doesn't appear to get lots of use.

P.S. Let me add a pseudo example:

f = (list) ->
  returning undefined ->
    for item in list
      console.log item
Aseem Kishore

I dig the appearance of -/>, but I also dig the readability and clarity of the void keyword. =)

Trevor Burnham
Collaborator

@trans Well, with undefined or return rather than null, yes.

@aseemk I dig the readability and clarity of void -> as well. What I don't like is the look of either (foo, bar) void -> or void (foo, bar) ->. That's why I think a different glyph would be preferable, and Coreh's proposed -/> stands out brilliantly.

Casey O'Hara

I also like @Coreh's propopsed -/> syntax. Agreeing with Trevor, void loses its appeal when the function has arguments.

7rans

It strikes me as too much of a perlism.

Michael Ficarra
Collaborator

@trans: void, the JS operator, is a "perlism" to you? Or were you speaking about the newest arrow syntax proposal?

I still think this discussion is ridiculous. Also:

$ coffee -bep 'a = -/> b/i'
var a;
a = -/> b/i;
7rans

newest arrow syntax proposal

Marco Aurélio

@michaelficarra I had thought about this, but I don't think it's going to be an issue for the parser. As far as I know, lexers produce tokens on a greedy manner, so -/> should be recognized as a token just fine.

If you're talking about breaking existing code, (since this addition will change the meaning of already valid programs) I don't think that's an issue either: I don't think any serious code is going to be subtracting regular expressions, as that will always result in NaN.

Of course, the code might call some of the methods of the regexp literal, which in turn could return a number, and the code would then subtract that. But such a combination of events (minus sign, regexp literal starting with > character, calling one or more functions on the literal that somehow return a number) is so rare, that I don't think it's going to be an issue.

Anyway, regardless of the syntax, I'm interested in the void functionality.

László Bácsi

+1 to this proposal. I've been bit by coffeescript's always returning strategy as well. Once it was with a function iterating over millions of rows while keeping all of it in memory and it's always an issue when using vows for testing. Our solution for vows was to create a global function U which would create a new function from the argument which returns undefined:

# helper for creating functions with explicit undefined return value
global.U = (f) -> -> f.apply(this, arguments); return

then we would use this helper to create asynchronous topics:

  "some context":
    topic: U -> Model.find('some_id', @callback)
    "some tests": (doc) -> # ...

I like the proposed -/> syntax, but you should also consider ~>. It's as simple as the other function operators (-> and =>) and I don't think it would be a problem to parse.

7rans

@lackac how did implicit return keep everything in memory? seems strange. and why all the trouble with U when you could have just added an undefined to the end?

The problem with the perlism (-/>) is that it's not functional in any other way. More benefit would com from a way to state any return value upfront.

f = (list) ->
  return undefined ->
    for item in list
      console.log item

Or

 f = (list) -|undefined|->
    for item in list
      console.log item
László Bácsi

@trans the function was iterating over millions of documents each of which got added to the _results array. Many of those documents were very big...

Now, I'm not saying that this was CoffeeScript's fault. Once I realized what the problem was I was able to fix this issue with returning explicitly at the end of the function. I just think it would be nice to have something for these kind of functions in the language.

As for the vows case, when you have a lot of asynchronous topics in your suite, having to add an explicit return to all of them makes it very ugly. The U helper cleaned up our test suites quite a bit.

Trevor Burnham
Collaborator

@lackac The problem with ~> is that it's practically indistinguishable from -> in many fonts (including this one...). I like how -/> really stands out. It's easy to spot, and easy to remember.

@trans In the abstract, I like the idea of being able to specify a return value up-front. But consider how it would work in practice. If I were to write

f = -|this|-> ...

then would that be equivalent to making this the last line of f, or would it capture the value of this where f is defined and return that? It's syntactically very unclear.

-/>, by contrast, is beautifully clear, and addresses the main pain point: having to add return to callbacks.

7rans

@TrevorBurnhma I understand what you are saying. But one thing I learned over the years, when a new "special syntax" is introduced it should always set off red flags. It may look "beautiful" for the isolated case, but when you take all such things en mass you get hard to read code. Hence the term a "perlism". It's bad for beginners especially, and it means another bit of memorization everyone must do. I would much rather see return have some sort of special "up front usage". (Note, I'm not too fond of -|foo|-> either, was just throwing it out there as brain juice).

Marco Aurélio

@trans But isn't the fat arrow/bound function already some sort of special syntax? The same goes for @, ::, and the ? operator.

Actually, the ? operator is even trickier for beginners, because it has different meanings depending on the space around it:

a? 1

compiles to

if (typeof a === "function") {
  a(1);
}

while

a ?1

compiles to

if (typeof a !== "undefined" && a !== null) {
  a;
} else {
  1;
};

But that doesn't mean the ? operator is bad, quite the contrary, it's one of the best features of coffeescript.

s.ross
7rans

@sxross

The only issue that I know of is that it adds some extra overhead, so it slows a program down a tad. Same things about #inject btw. So most experienced coders end up avoiding both.

With node, the transpiler could avoid any such overhead, so that should not be a problem. So in this case I'm thinking void and a special return syntax that look like regular code more or less but get some special treatment.

7rans

@Coreh Of course there are always going to special notations. Those are good things and necessary. But it doesn't mean everything can or should be so. It's a "Goldilocks". So we should be diligent and only add such things if they are really really really better.

To get an idea of just how crazy things can get in this regard check out APL. Here is an example: http://aplwiki.com/PascalsTriangle/Solution.

Alex Wayne

+1 for -/>

Satoshi Murakami
Collaborator

-/>
kind of gives the impression that the flow of information is somehow "cut" or "forbidden"

Exploring further:

  • -/> =/>
  • -|> =|>
  • -<> =<>
  • ->< =><
  • -< =<
  • +> #>
  • .> :>
  • ...
Casey O'Hara
  • +-> +=>
  • -+> =+>
  • -*> =*>
David Chambers

There's a pleasing symmetry between -> => and .> :>. Additionally, two characters is better than three, and . does a decent job of conveying "nothingness" (though not nearly as good a job as void).

Forced to choose right now, I'd vote for .> and :>. That said, I don't have a strong opinion as to whether such syntax should actually be added to the language.

Ryan Scott Lewis

-> =>
-] =]

To me, the greater than symbol indicates to "pass" and the right bracket indicates "stop".

Jeremy Ashkenas
Owner

I think that now that this discussion has settled down to symbol bikeshedding, it's ripe time to give it mercy.

Creating a special type of function to indicate that it should not return a value is a very poor idea. Let's go through the points:

The only places where you'd want to use void are places where you're aware that you want to return undefined -- either because you're concerned about the efficiency of that particular function, or because the API is asking for it. In either case, it's just as easy to write return as it is void.

Every function returns a value, even if that value is nothing more than undefined. If everything in CoffeeScript is an expression ... then every function should be a value, and calling any function should return a value.

If the shape of a function in CoffeeScript is (input) -> output then the natural and proper place to look for the output of a function is at the end of a function body -- early returns aside. Tagging a function body as void up front puts that declaration in the wrong spot.

The biggest problem with this proposal is that is makes it possible, easy even, to write hypocritical code:

showInts = (list) void -> 
  console.log x for x in list when isInt(x) 
  return list

Now your code says it's going to do one thing, and actually tries to do another. The syntax of a language should not make this sort of nonsense easy to write.

Closing the ticket.

Jeremy Ashkenas jashkenas closed this
Trevor Burnham
Collaborator
7rans

Wouldn't something like ruby's returning method suffice, and be more versatile and not suffer the speed penalty of Ruby's thanks to transpiler optimization. Something like:

showInts = (list)
  return undefined -> 
    console.log x for x in list when isInt(x) 

?

Trevor Burnham
Collaborator

The return x -> syntax is a non-starter in CoffeeScript; because functions can be passed around, return x -> already has a valid meaning. And even if that weren't an issue, I think the syntax would hurt rather than help readability.

7rans

What does return x -> mean? I was wondering about that.

In any case, that is just an example, I'm sure something else can be used, such as returning x ->.

I think it's much more readable than -/> which is something that you have to look-up to understand --it doesn't "read" at all.

dimatter

having a return at the end of a function call breaks nowjs framework :(

Trevor Burnham
Collaborator
Michael Ficarra
Collaborator

How is that possible?

spoiler: It's not.

dimatter

@TrevorBurnham yeah , returns were not the reason for my code failing. It started working shortly after I wrote the above comment. Don't know what was wrong :-x

edvakf

I also want a function that doesn't IMPLICITLY return the last statement, which doesn't necessarily mean it always returns undefined even if I EXPLICITLY return something.

I would say *-> would be more suitable than void -> for that reason.

Having said that, in ES6 spec, there may be a function that always implicitly returns the last statement. When the spec is almost fixed, CoffeeScript could decide to adopt that notation in addition with ->, because, I think, it will be hard to change -> to not explicitly write a return.

Drew Pirrone-Brusse

So I just picked up CoffeeScript---JavaScript, too, actually---so I might be a bit premature in weighing in my two cents. I'm actualy here, though, because I was getting caught up on compound comprehensions and trying to glean a complete understanding of CoffeeScript's implicit returns. It's not that I found them tricky or am taking fault in them, it's just that I must have missed the memo when I was skimming reading through on the Little Book on CoffeeScript. As someone who is brand new to this language, I do have to take issue with some of the justifications made here.

@jashkenas, you said a couple times that one of the habits CoffeeScript is designed to promote, if not force, in its user-base is paying close attention to the return values of functions. It strikes me, though, that the syntactic sugar of implicit returns (as opposed to a syntax error in the absence of one) does just the opposite. And sure, the major headdesk experiences that were related in (or, you know... This whole issue?) will definitely hammer home the point that this language isn't as light'n fluffy as it looks, and you need to always be aware of what it is you're doing.

If, though, for example, when reading about function definitions I had come across this third kind of arrow that denotes the complete lack of a return value, it would have, as @trans suggested it should, set off red flags. I wouldn't have needed to chase down this issue in order to really understand how important this is. It would have made me think about the fact that every other kind of arrow must then return something. Which is awesome.</shameless_praise>

There are definitely potential problems with this kind of syntax, especially in cases where programmers slip or get lazy. I would posit that those issues are the exact same issues that come along with potentially forgetting about an implicit return, though. Even with the inversion of control flow that specifying void in the body, rather than in or at the end of a function entails---which does put me ill at ease, mind you---I would be thrilled to see this syntax in CS.

Well... That was a novel I didn't expect to write tonight. Keep up the good work.

Eirik Albrigtsen

Sorry to beat the dead horse on this,
but I do think the current state of things can really screw you over, and
I would ask this is taken up again for reconsideration.
Bear with me, I'll try to explain as best I can.

1. Classes and constructor functions

class Ex1
  constructor: (@a) ->

class Ex2
  constructor: (a) ->
    @a = a

These two both work as intended with the class keyword, even though it goes against most coffeescript logic.

The Ex1 constructor does exactly the same thing as the Ex2 constructor,
but since the body is captured in the destructuring argument no returns
are ever issued for this function.

Ex2 will not produce a return in the js as it is a class constructor - i.e.
it is special cased.

Now compare if you were to construct these classes prototypically
via a constructor function:

Ex1 = (@a) ->

Ex1 will again work because the body of the constructor is inlined.

Ex2 = (a) ->
  @a = a

Ex2 will return @a as it is not understood to be a class constructor.
This normally isnt a problem (and it can easily go unnoticed when used to
the 3 other cases above) because the return value of a constructor only
changes the output via a new Ex2(arg) when
arg is an Object.

It is clear that you already special case class constructors
(probably for this reason), but it does not affect the state of things
when you want to construct your classes prototypically.

It is also very common to try to modify a class starting out looking
like Ex1 to something more like Ex2 when arguments need preprocessing,
then suddenly finding yourself having a class that sometimes breaks depending
on whether or not arg is an object.

2. Implicit returns are only implicit at the end

A simple case:

fn = ->
  if a
    a
  else if b
   b

This works as intended, but you notice that the else statement is unnecessary
because if !a, the function has returned.
Unfortunately, the optimized version does not work as believed:

fn = ->
  if a
    a
  if b
   b

This only returns if b.

Adding statements

fn = ->
  if a
    a
  statement
  if b
   b
  c

This only returns c, and also end up discouraging the partial breakout return style
that I tried to emulate, and instead encourage several other bad styles:

First, the unreadible, inconsistent return breakout-style:

fn = ->
  return a if a
  statement
  return b if b
  c

Second, the verbose if-else chain that only works at the end:

fn = ->
  if !a then statement

  if a then a
  else if b then b
  else c

Third, the non-breaking out

fn = ->
  if a
    a
  else if b
    statement
    b
  else
    statement
    c

The third also commonly seen with some returns littered randomly
around the if-else clauses.

Obviously, while this is taken to the extreme, these are all pretty stupid.

3. It encourages being too clever

Firstly, the bad styles above are encouraged to avoid writing return,
but usually this only succeeds partially around a library and ends up making
functionality less easy to read, and also the language look less consistent.

Secondly, we also try to get away with not adding an extra line
for a blank null/return
because we know the result of the last call is null/undefined already. E.g:

fn = ->
  console.log('i know this returns undefined, so an extra return/null is skipped')

This makes it very hard to reason with the code you are not familiar with,
or similarly code you wrote more than 6 months ago, because you just have to assume
things return sensible things without being able to see it immediately.

4. A huge part of js functions are side-effects only

Side-effects functions all suffer from problems already discussed in this issue,
they tend to not return anything and you have to make it explicit at the end by
typing null/return when it is just annoying to do so.

This is ultimately a very big annoyance, because it seriously detracts from the
big advantage coffescript has over javascript: 33% less code for roughly the same
functionality. Blank returns are also ugly when all you want to do is return
from a really nice one-liner:

fn = (args...) ->
  log.apply(l, [fn].concat(args))
  return

The end

This issue isnt just about whether or return or null is too hard to write, but that
having them implicit in the first place is a bad idea in cases where the function
is not a perfect fit for it (a lot of cases in my opinion).

I understand that my complaints in point 2 can be solved entirely by being
disciplined when programming, but I was simply making a point that it encourages bad
behaviour. Ultimately I would like to always write my returns,
and opt in to the implicit returns but that would obviously break backwards
compatibility.

Therefore, I think that having a symbol to indicate that this is a side-effect
only function (and make it a syntax error if return is issued in such a fn), is a uniformly great idea;
it reduces what is necessary to write, and it makes such a fundamental property
of a function immediately obvious from the declaration.

As the -> indicates a mapping, why not use one of the closing flow symbols
suggested above for it. I would like to add the suggestion for
=| and -| to indicate a fork / side effect function without typing an extra
character.

Ok, I am done. Sorry about the wall of text.

Michael Ficarra
Collaborator

@clux:

  1. It's completely reasonable for the abstract concept of a "class" to be defined differently depending on the syntactical construct used to define it. Constructor functions in class blocks don't implicitly return since that's not the commonly desired behaviour. For functions in general, though, that's not the case.

  2. This is an argument against implicit returns in general, and that's just not what we want. CoffeeScript functions return the value of the last expression when execution reaches the end of the body (instead of the less meaningful undefined like in JS). In a similar way, the value of a conditional expression is the value of the last expression in the path it takes. So the behaviour here is entirely consistent.

  3. If somebody wants to avoid being "too clever", they can just use explicit returns everywhere. Again, this is just a bad argument against implicit returns in general. We want implicit returns.

  4. I don't think very many JS developers are using a procedural style. If they are, they're missing out. Assuming they are, what's wrong with just ignoring the return value of said procedures? If you're worried about the overhead of collecting the return value, it's just as easy to explicitly define a return value as it is to use a more obscure syntax at the top of the function, where one wouldn't naturally look to understand the return value.

I'm not convinced at all. Still a very strong -1.

Eirik Albrigtsen

@michaelficarra
You are right that a lot of the arguments I presented are against implicit returns in general because of the problems I feel they introduce.
Judging by this thread, I doubt I am alone in thinking that. The opt-out syntax is desirable because it is a compromise.

I completely disagree with your argument against 4 (just using the return value). I am not worried about the overhead,
I am worried about the crazy side-effects:

  1. jQuery event callbacks, if the last line calls a manipulating function that for some reason returns false, the event stops bubbling. This is not easy to detect, because you do not need bubbling all the time.

  2. construction as a side-effect of a vanilla constructor function. so returning this.a like above, can (sometimes) break the function. This is not easy to detect because the construction will most likely work most of the time.

  3. module loaders, using module.exports, but overridden by the return value of the final function
    (which could have come from another implicitly returning CoffeeScript function)

  4. Any library that takes a callback could use the return value of the function. Most probably do not consider that many CoffeeScript functions implicitly return garbage.

Besides, changing one character at the very beginning of function declarations is hardly the same as as adding an entire line,
to every side-effect function. I try to use explicit returns everywhere in all my functions because of the danger of accidentally returning something unwanted. I would rather know that a function is safe by examining the function declaration, than hoping that the author knew what he was doing.

As you say: just use the return values. This is what everyone wants to do, because there is less code involved.
But it comes with a price. A completely avoidable price:
New syntax force one google, then you know what it means forever.
Implicit returns force you to figure out what is bad by experiencing all the errors first hand, and always thinking about them in the future.

Michael Ficarra
Collaborator

[...] if the last line calls a manipulating function that for some reason returns false [...]

I hate that argument.

If the return value of a function is being observed and acted upon, it is, by definition, NOT a callback.

Callbacks are in the tail position. They replace the currently executing function (well, technically they can't do that until es-next, but that's a spec. bug). When you have fears that passing a callback function to an event listener may produce a "bad" value, I just stop listening. Decide whether you want to care about your return value or not, don't play both sides.

construction as a side-effect of a vanilla constructor function

Constructors are already capitalised as convention. Just like the developer thinks about capitalisation and thinks about the fact that they will be using the new operator on them, they can think about the return value when not using our special syntactic construct that makes that assumption for them (class). Why would you type new f (not capitalised) without considering that f needs to return a non-object in order to produce the new instance? That's careless. And it would be just as careless if you're defining F = -> ... (capitalised) and not thinking about the return value. The thing about implicit returns is that you don't have to specify your return value, but you still have to consider it. This suggested syntax doesn't help with that. It actually just obscures the occasions when it is specified because it can be specified in more than one area (the head and the tail), even simultaneously.

#3 is the module loader's fault and #4 is a re-iteration of #1.

changing one character at the very beginning of function declarations is hardly the same as as adding an entire line

This isn't Perl. Our syntax isn't a symbol soup, but it's not overly verbose either.

I would rather know that a function is safe by examining the function declaration, than hoping that the author knew what he was doing.

Your syntax does not protect you from early exits. You still have to analyse the function. Only implicit returns are protected, and those can be just as easily observed by looking at the single last expression of the function.

Implicit returns force you to [...] always thinking about them in the future.

As I said above, you will always have to be thinking about your function's return value (assuming it's being used), regardless of whether it's specified implicitly or explicitly, at the head or at the tail. Even more so when the shitty libraries everyone seems to use are making sure a callback's return value isn't on their no-no list...

Eirik Albrigtsen

When you have fears that passing a callback function to an event listener may produce a "bad" value, I just stop listening.
Decide whether you want to care about your return value or not, don't play both sides.
[..]the shitty libraries everyone seems to use

Unfortunately, the definition of a callback matters little to how people write code. People are passing functions around and doing things with them.

I know you have many legit arguments here, and I am sorry if this annoys you, but you can't defend own behaviour by calling other libraries shitty. Most code has flaws, and your code will often have to interface with flawed code. When that happens, I would rather my code made it absolutely unambiguous whose fault it was when something goes wrong.

If I leak data through a passed in function, I am partially to blame if whatever weird API I am using optionally acts on that.

This suggested syntax doesn't help with that.
It actually just obscures the occasions when it is specified because it can be specified in more than one area (the head and the tail), even simultaneously.

That's why the suggestion is up to make non-blank returns from such functions a syntax error.

As for the prettiness/ugliness of said syntax, I can't argue with that. It's ultimately not my language.
I feel it makes sense, but we clearly disagree about the need for it anyway. Thanks for your time, at any rate, I am sorry to have been a bother.

Simon Greenwold

For what it's worth, I just spent a long time figuring out an issue related to this. In the jake build tool, asynchronous tasks must end with a call to "complete()" which emits the necessary event to trigger the continued execution of the build process. I was porting a JS Jakefile and I allowed these functions to end with complete, which did the wrong thing. Returning the function caused the build script to terminate. So the script would complete the one build step and never progress.

I'm new to CS, just learning it. And I have now learned this lesson. At the very least, more could be made about the subtle mistakes this can cause in the introductory materials. I'm pretty sure it would have rang a bell and saved me a bunch of debugging. I'm not saying the language should change. I'm liking it very much. It would be useful to beginners to have the potential pitfalls of accidentally returning value called out clearly in the "Everything is an Expression" section of the overview. Maybe just an example of how to prevent it (other web pages are suggesting people return "true", so they clearly didn't get the message) would be sufficient. It's obvious it should be a naked "return" as soon as you're shown it once, but not until.

Matias Meno

Would it be safe to assume that really only loops as last statement are a concern here, since they have to be collected? I don't see any other case of implicit returns that would cause a problem here...

Michael Ficarra
Collaborator

@enyo: You're correct, which is exactly why we should be required to explicitly differentiate between loops that collect values and loops that just iterate. One of the two big changes I would make to CS if I could.

Aseem Kishore

Loops is definitely one case, but I thought some other use cases have been covered in this thread, like callbacks where the return value matters (e.g. jQuery return false canceling events).

@michaelficarra, what's the other big change you would make? =)

Matias Meno

@michaelficarra That's what I thought.. so the problem is not actually if functions implicitly return the last statements. The discussion should be focused on loops.

Matias Meno

@aseemk Mh, yes but on that matter I would disagree on a syntax change. If a value is expected to return something, than the last statement should reflect it.

Michael Ficarra
Collaborator

@aseemk: Sure, I was kind of ignoring that reason. The other change I would make is to the class syntax, especially the context within a class body (it should be the prototype rather than the constructor). See all the executable class body issues for the various opinions about that.

Nijiko Yonskai

~>

Simple.

Nick Retallack

I've used CoffeeScript for about two years now, and this one gotcha still strikes me now and then. Usually this is harmless. However, there are a few cases where it can really screw things up.

Case 1: Cancelling event handlers. If an event handler returns false, it is the same as calling event.stopPropagation(); event.preventDefault(). If you write an event handler that sometimes returns false, you're in for some very difficult-to-find bugs.

Case 2: Wasteful memory allocation. If the last statement in your function is a loop, CoffeeScript will generate a list for you. While it's building this list, it could be wasting a lot of valuable memory. If you're running on Mobile Safari, you have very limited memory, and using too much will cause Mobile Safari to crash. This is a serious problem. I have met several rather vocal CoffeeScript opponents who consider this accidental creation of huge lists to be the #1 reason they do not use CoffeeScript. Memory allocation in JavaScript is very slow, so if you care about performance, you don't want this to ever happen. Also, this use case produces some needlessly obtuse JavaScript.

Normally, in JavaScript, if you don't specify a return value, functions return undefined. In a lot of cases, unless I'm writing a simple one-liner, this is what I want. To get this behavior in CoffeeScript, I currently have to type undefined as the last line of every function that shouldn't return a value. This is pretty ugly, and seems against the spirit of CoffeeScript.

To maintain backward compatibility, I propose that we add a simple new syntax to get functions that require explicit return values. They could look like --> instead of ->, or ==> instead of =>. Or something similar. Anything shorter than typing undefined all over the place would be lovely.

Nick Retallack

Even more so when the shitty libraries everyone seems to use are making sure a callback's return value isn't on their no-no list...

Excuse me, but this problem is not isolated to "shitty libraries". All web browsers will cancel your event if you return "false".

Essentially, I just want to be able to write functions like in regular JavaScript, where they return undefined by default unless you explicitly return something else.

Accidentally returning stuff is wasteful and degrades performance. We got this implicit return value thing from Ruby, and you know Ruby isn't known for its performance.

David Chambers

Much of the time, a function exists to perform some sort of transformation on its argument(s) and return a value. Being able to do this is very nice:

square = (n) -> n * n

Some languages try to have their cake and eat it, too:

def square(n):
    return n * n
square = lambda n: n * n

My view is that one construct is better than two, in this case. One is free to use return in every function if implicit return values are causing problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.