Closed TrevorBurnham closed 13 years ago
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.
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......
:)
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.
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 return
s, 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.
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.
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.
The look of *->
is puzzling. void
seems clearer to me.
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.
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.
I agree with satyr. While void
is certainly more keystrokes than *
, it is less than return
. It's more succinct than both.
console.log 'This function has no side effects'
Self-contradiction? (Logging is a side-effect.)
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.
@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.
@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?
@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.
@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 return
s hurts the readability of JS output.
@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).
@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
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.
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.)
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.
Wow. I like that syntax a lot.
On Jun 26, 2011, at 11:36 AM, Corehreply@reply.github.com 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: https://github.com/jashkenas/coffee-script/issues/899#issuecomment-1441593
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
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
I dig the appearance of -/>
, but I also dig the readability and clarity of the void
keyword. =)
@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.
I also like @Coreh's propopsed -/>
syntax. Agreeing with Trevor, void
loses its appeal when the function has arguments.
It strikes me as too much of a perlism.
@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;
newest arrow syntax proposal
@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.
+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.
@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
+1
@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.
@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.
@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).
@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.
In Rails, there used to be a construct:
returning foo do
end
This isn't language magic -- it's Ruby magic, so without thinking about it too much I can envision:
returning(undefined, ->
)
Which you could special-case to:
voidReturn(->
)
This is like the U function mentioned previously. Was there a problem with this "promise" DSL? Do you remember, @trans?
Sent from my iPhone
On Jun 27, 2011, at 9:19 AM, Corehreply@reply.github.com wrote:
@trans But isn't the fat arrow/bound function already some sort of special syntax? The same goes for @, ::, and the ? operator.
Reply to this email directly or view it on GitHub: https://github.com/jashkenas/coffee-script/issues/899#issuecomment-1448443
@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.
@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.
+1 for -/>
-/>
kind of gives the impression that the flow of information is somehow "cut" or "forbidden"
Exploring further:
-/>
=/>
-|>
=|>
-<>
=<>
-><
=><
-<
=<
+>
#>
.>
:>
+->
+=>
-+>
=+>
-*>
=*>
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.
->
=>
-]
=]
To me, the greater than symbol indicates to "pass" and the right bracket indicates "stop".
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, the proposal was (at least in my mind) to not only disable implicit returns in these "void functions," but also to make explicit returns like those in the hypocritical code you describe a syntax error. (Ending such a function with a no-op like "2+2" would, ideally, yield a warning.)
It's rare for there to be this much interest in a feature based on actual experience using CoffeeScript. I hope you'll reconsider it.
On Jun 30, 2011, at 10:14 PM, jashkenasreply@reply.github.com wrote:
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 returnundefined
-- 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 writereturn
as it isvoid
.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 asvoid
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.
Reply to this email directly or view it on GitHub: https://github.com/jashkenas/coffee-script/issues/899#issuecomment-1480351
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)
?
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.
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: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 trailingreturn
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 becomeAnd underscore.coffee wouldn't have to grow 2 lines longer in order to fix
_.mixin
and_.times
:The
void ->
syntax is not only more succinct, but also more self-documenting than thereturn
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 seevoid
, 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, compareto the current
A pretty significant readability improvement, don't you think?]