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

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

Closed
TrevorBurnham opened this issue Dec 5, 2010 · 73 comments
Closed

Comments

@TrevorBurnham
Copy link
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?]

@jashkenas
Copy link
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.

@delfick
Copy link

delfick commented Feb 12, 2011

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......

:)

@TrevorBurnham
Copy link
Collaborator Author

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.

@TrevorBurnham
Copy link
Collaborator Author

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.

@TrevorBurnham
Copy link
Collaborator Author

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.

@jashkenas jashkenas reopened this Jun 16, 2011
@sxross
Copy link

sxross commented Jun 16, 2011

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.

@satyr
Copy link
Collaborator

satyr commented Jun 16, 2011

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

@sxross
Copy link

sxross commented Jun 16, 2011

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.

@TrevorBurnham
Copy link
Collaborator Author

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.

@caseyohara
Copy link

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

@satyr
Copy link
Collaborator

satyr commented Jun 16, 2011

console.log 'This function has no side effects'

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

@TrevorBurnham
Copy link
Collaborator Author

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.

@michaelficarra
Copy link
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.

@TrevorBurnham
Copy link
Collaborator Author

@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?

@michaelficarra
Copy link
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.

@TrevorBurnham
Copy link
Collaborator Author

@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.

@michaelficarra
Copy link
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).

@TrevorBurnham
Copy link
Collaborator Author

@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

@aseemk
Copy link
Contributor

aseemk commented Jun 17, 2011

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.

@masonmark
Copy link

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.)

@coreh
Copy link

coreh commented Jun 26, 2011

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.

@TrevorBurnham
Copy link
Collaborator Author

Wow. I like that syntax a lot.

On Jun 26, 2011, at 11:36 AM, [email protected] wrote:

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

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

Reply to this email directly or view it on GitHub:
#899 (comment)

@trans
Copy link

trans commented Jun 26, 2011

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

@trans
Copy link

trans commented Jun 26, 2011

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

@aseemk
Copy link
Contributor

aseemk commented Jun 26, 2011

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

@TrevorBurnham
Copy link
Collaborator Author

@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.

@caseyohara
Copy link

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

@trans
Copy link

trans commented Jun 26, 2011

It strikes me as too much of a perlism.

@michaelficarra
Copy link
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;

@dimatter
Copy link

@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
Copy link

edvakf commented Oct 21, 2011

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.

@pyrrho
Copy link

pyrrho commented Nov 8, 2011

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.

@clux
Copy link

clux commented Dec 4, 2011

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.

@michaelficarra
Copy link
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.

@clux
Copy link

clux commented Dec 4, 2011

@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.

@michaelficarra
Copy link
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...

@clux
Copy link

clux commented Dec 5, 2011

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.

@juniperoserra
Copy link

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.

@enyo
Copy link

enyo commented May 18, 2012

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...

@michaelficarra
Copy link
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.

@aseemk
Copy link
Contributor

aseemk commented May 18, 2012

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? =)

@enyo
Copy link

enyo commented May 18, 2012

@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.

@enyo
Copy link

enyo commented May 18, 2012

@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.

@michaelficarra
Copy link
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.

@nijikokun
Copy link

~>

Simple.

@nickretallack
Copy link

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.

@nickretallack
Copy link

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.

@davidchambers
Copy link
Contributor

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
Projects
None yet
Development

No branches or pull requests