Open aleclarson opened 6 years ago
There was some side discussion of this in https://github.com/jashkenas/coffeescript/issues/4847#issuecomment-356124744, but this issue could perhaps serve as a better canonical place to track it.
Here’s how I remember the consensus so far:
var foo, bar, baz;
line at the top of a functional scope, the compiler would output a let foo, bar, baz;
line at the top of the block scope.What about preserving the existing behaviour, while also allowing you to explicitly define a variable at the block level without hoisting it to the top (i.e. enable the use of var/let/const keywords and have it output directly as written)?
@aminland See the quote from above:
There should only be one way to declare variables in CoffeeScript. Not having to think about two different mental models of variable scope at the same time is precisely and specifically the raison d’etre of CoffeeScript in the first place.
Any solution that preserves the existing behavior while also allowing some other way to declare a variable means you now have two ways to declare variables. The consensus from the earlier issues was that preserving the simplicity of there being only one way to declare variables is our top priority. So really the choice is only between “do nothing” and “change the current output to be block scope instead of function scope.”
If those are the only options then let's please not break backwards compatibility... Block scoping wouldn't actually enable you to do anything you can't currently accomplish with function scoping...
preserving the simplicity of there being only one way to declare variables is our top priority
Backwards compatibility is probably the top priority, since we don't want to fracture an already dwindling community. Also, block scoping shouldn't be written off as too complex. The Javascript community seems to be thriving with the addition of block scoping. There is clear demand for such a feature in Coffeescript. I would argue the greater risk is not matching Javascript on that flexibility.
With that said, I think the best direction is probably adding the let
keyword. Familiarity is important in reducing the mental barrier of switching between Javascript and Coffeescript.
i think block scoping is most correct and i would definitely never thoughtlessly define a variable within a for loop (for example), instead defining its initial value outside the loop if that's the intent. but while i know i would be able to migrate my code if all variable declarations became let
by default, i also know i am in the minority - for everyone else it just 'breaks things randomly' (from their perspective), or they simply cannot upgrade.
so, unless there is a convenient way to use optional flags for such feature ({"blockScoping":true}
, --blockScoping
) then i think allowing use of the let
keyword may be the only real possibility.
For the record, if block scoping were to be added to CoffeeScript in addition to the function scoping we already have, it would be via a new operator like :=
, not the let
keyword. Adding any new keyword would be a breaking change, whereas :=
would not be. Please review the top post and the discussions it links to before commenting.
Adding any new keyword would be a breaking change, whereas := would not be
let
is a reserved keyword since no later than v1.2 - see also your own comment: #58 "neither let/const nor := would break backward compatibility"
For the record, if block scoping were to be added to CoffeeScript in addition to the function scoping we already have, it would be via a new operator like :=, not the let keyword. [...] Please review the top post and the discussions it links to before commenting.
For the record, it is clear from reading said comments that this is your opinion on the matter, and far from a universally-accepted truth. While I did not actually mean let
specifically but a new keyword/operator in general, and in fact I may prefer :=
, let's err on the side of not telling new participants to take a hike.
You are correct, let
is reserved. Regardless, in the prior threads there was a consensus that if we were going to support both types of scoping, it would be via :=
. Later on, a consensus formed that having two types of scoping is too complicated for CoffeeScript and we would rather keep the current scoping rather than have both. Hence the consideration for a potential future 3.0.0 breaking change where the one and only supported scoping becomes block scoping rather than function scoping.
Furthermore, it seems a new consensus has been established in regard to "block scoping by default" being too drastic of a breaking change, which has a strong possibility of fracturing the community.
I think the argument between let
or :=
can be logically put to rest for the following reason. The :=
operator works better in expressions where the value is being assigned and returned simultaneously.
# Assign `y` with block scoping, then add its value to `x`
x + y := 1
# Looks like a syntax error
x + let y = 1
But I'm warming up to the idea of a package-specific flag to enable block scoping by default. Of course, you could argue this is toxic for open-source projects, since it would not be obvious whether the flag is being used without checking the configuration. But that should be their choice, instead of the language gate-keeping a useful feature.
I wish CS could change to block-scoping without causing further abandonment. I wish someone could make a sophisticated tool to help migrate code for this change. Or, I wish there were an elegant way to opt-in to new features per file/module. Unfortunately, adding a keyword or operator is simply so much easier than all of the above that it remains tempting.
Of course, "leave it like it is" may be good enough for people like me who simply prefer CS, not because of one particular feature or another.
Some (admittedly distasteful) ideas:
use
statement at the top of the file specify something other than 'strict'
(ie. compiler options)?I agree, it’s an annoying problem. It’s problematic to increase complexity when one of our prime selling points is simplicity.
We could mimic ES5’s 'use strict'
and add in-file configuration, e.g.:
'use block scope'
if yes
x = 'block scoped!'
This is probably better than an extra compiler flag, for the reasons mentioned above (not knowing whether a file is written to be block or function scoped without looking elsewhere in the project, etc.). But is it really better than :=
? It certainly doesn’t seem simple, and I haven’t seen JavaScript follow this pattern since 'use strict'
. It also feels like it opens a crack in the door for all sorts of other compiler flags, which further degrades our simplicity.
I'm a pretty big fan of block scoping as a default and think it would be a lot more intuitive for new users. I'd happily update all of my projects to properly support it assuming block scope became a default. I think mirroring modern JS here would have a lot of advantages long-term and be worth the intermediate pain.
I suspect most of my projects would require little if any effort to support a switch to block scope. An automatic upgrade tool could presumably hoist all variables to the top of function scope, no?
I agree with @zeekay. Block scoping by default is the way forward, and the upgrade path looks smooth. The JS community is evidence that the majority prefer block scoping. Anyone who disagrees can keep using CoffeeScript 2.
Meh... In CoffeeScript, expressing a function is very quick and easy, so there is no need for the language to infer tighter scopes to keep things local. On the few occasions where block scope would improve anything, we can just wrap that code in its own lexical scope with a few extra characters.
Lexical scope, based around functions (with associated concepts, like closure) is a beautiful idea that integrates concepts in a really elegant way. It all works so well in CoffeeScript that I personally think things are correct as they are.
I wote for :=
.
I think, that let
, const
and other trash should be avoided.
Language must be clean.
And I hope, that this error at least will be fixed: https://github.com/jashkenas/coffeescript/issues/4723 Because incapsulation in CoffeeScript sucks.
I use it just for clean syntax, also often use JS to avoid problems.
that's not a bug, that's expected behaviour how else would you make the value available outside the function without returning it
My two cents after a year of straight ES coding: I found that because of linters like eslint I had to go back through and hand tune all the vars to let and const from cs.
If we had linting working well in CS, I could convince people to allow CS checkins. As it stands now though, I am stuck.
I personally think we should never worry about such things in CS and generate let and const appropriately. I see mistakes in ES all the time with the use of let and const, but var is all kinds of taboo at the moment.
I personally think we should never worry about such things in CS and generate let and const appropriately.
I did make an experimental branch that simply always output let
wherever we currently output var
, and it worked, with all the tests passing; though it was considered a bad idea because using let
but always corresponding to function scope was felt to be misleading.
If it’s possible to reliably track variable references and assignments, which I think it might be, then we could output let
and const
right now, though they would still be relative to function scope (because to switch to block scope would be a breaking change):
const
declaration.let a, b, c;
declaration line at the highest relevant block scope, which at worst would be equivalent to that function scope.If we know a variable isn’t being referenced, I wonder if we need the declaration line (var a, b, c;
); but I’m assuming it’s there for a reason.
But the fact that our let
and const
output would always still be relative to function scopes, even if occasionally we can declare the variables within a block scope that’s inside a function scope, makes the fact that we’re outputting let
and const
less successful. Without it being constrained to block scopes, it wouldn’t correspond to idiomatic ES2015 output very well, and might still be misleading.
Late chime in but I do miss const. I would love to adopt CoffeeScript if it offered const as a storage modifier. Enums schemas regexes and identifiers are always const for me & have been since const was made available
I also agree with switching var
to let
in the compiler
I'd like to point out that there is, in fact, already two ways to declare variables in CoffeeScript, so it's just a question of whether it's worth making it simpler. Specifically, you can introduce new block variables with do
:
do ({ foo, bar } = {}) ->
# ...
Are we the only ones doing this? 😀
Granted, technically, this is just function scope, but the point is that, in cases where you want to be sure of the scope, you can already do that. The choice already exists: the question is whether a block-assignment operator would be simpler and more elegant. Which I would think is self-evidently the case. 😊
I think this is worth exploration. As far as I'm aware, do (a) =>
output could already be changed to using let
without a breaking change, avoiding the IIFE in favor of true block variables.
// current
((a) => {/* ... */})(a);
// could be
{let a = a; /* ... */}
Fat arrow is probably what you want in most cases and as far as I can recall losing this
from using a do ->
IIFE has done nothing for me except having to come back and change the arrow when the need arose.
Can anyone think of a case where you'd want to intentionally lose this
?
@Inve1951 Neat point about changing the output of do (...) =>
. And if the block doesn't use this
, it could also work for do (...) ->
. While there may not be much use for losing this
via ->
, it seems natural to preserve the semantics in this case.
Back to whether to make something more convenient than do
, the main use-case I have for block scoping is for
loops. I can't tell you how many times I've written code like this:
for item in loop
do (item) ->
callbacks.push -> ... item ...
#or
callbacks.push do (item) -> -> ... item ...
I'd much rather write something like
for let item in loop
callbacks.push -> ... item ...
#or
for const item in loop
callbacks.push -> ... item ...
I can't think of how to write this with :=
, but maybe I'm missing something. Given support for this, I think it would also be natural to allow (but not require) declaring variables as block scope via let x = ...
or const x = ...
(or just one, probably let
, if we want to avoid complexity). This would also make it far easier to add TypeScript support to CoffeeScript; in particular, it would enable using :
as the type operator just like in TypeScript, and I think it would make for more natural placement of existing jsdoc typing comments.
As none of this breaks backward compatibility, I think it could be added to CS 2.
@edemaine I share the pain with loops and callbacks.
Here's a proposal in the scope of CS3:
Change the for item in items
output to use let
block-scoped variables unless an item
identifier is already declared, e.g. via item = null
just prior the loop.
Example:
for item in items
console.log item
New output:
for (let item, i = 0, len = items.length; i < len; i++) {
item = items[i];
console.log(item);
}
Example 2:
item = null
for item in items
console.log item
New output:
var item;
item = null;
for (let i = 0, len = items.length; i < len; i++) {
item = items[i];
console.log(item);
}
This also leaves a clear upgrade path for code relying on the current behavior - Simply declare item
beforehand.
Aside from this use case (loops) I'm still not convinced we need or even want block-scoped variables (except for aesthetics).
There's one pain with do (a) ->
that could maybe get alleviated: It gives you reference errors when a
is not declared. This could be fixed with a typeof
check in the output akin to a ? undefined
for the parameter / RHS of assignment, but isn't free or particularly readable.
Though, again, the only times I ran into this was with loops when I simply wanted to re-use an identifier from outer scope and that outer scope code changed, no longer using that identifier.
Above proposal would not fix this.
I know assignment scope has been discussed at length for CS2 (#4951), but I don't see a discussion in the context of CS3. So here it is.
Block scope
=
I'm strongly in favor of changing
=
fromvar
tolet
and using nearest block scope instead of function scope, as was proposed by @jashkenas here: https://github.com/coffeescript6/discuss/issues/58#issuecomment-265220724Shadowing avoidance
There was also talk of adding a
:=
operator for explicit avoidance of shadowing.Another option is the
let
keyword (which invites inclusion ofconst
).Just today, I thought of
:foo = 1
as another option.Concerns
@jashkenas said in https://github.com/coffeescript6/discuss/issues/58#issuecomment-265193752:
..which rules out the
:=
operator, but maybe notlet
or:foo =
?If
=
is changed to block scope, the shadowing problem can be fixed without having two mental models of variable scope.In the same comment, @jashkenas said:
..but I assume this discussion is still "up in the air" for CS3.
Let me know if I missed any other ideas or concerns.