Closed coffeescriptbot closed 6 years ago
From @DomVinyard on 2016-12-05 13:42
Strongly dislike :=
, it's way too ambiguous as a visual metaphor. If you were introducing entirely new semantics, why not stick with the word let
(for the sake of a couple extra keystrokes), however my vote would be to not expose two types of assignment.
Everything is var
, or (probably is preferable) everything is let
. Not both.
From @edemaine on 2016-12-05 13:47
@GeoffreyBooth You've probably thought about this, but why are you hoisting the let
s to the top of the block instead of just leaving them at the assignment? Your example could alternatively be compiled to
var a;
a = 1;
let b = 2;
if (true) {
let b = 3;
}
The difference is in treatment of the temporal deadzone. Your compilation removes the deadzone, so use of a variable never causes an exception, while this compilation causes e.g. f(b); b := 3
to throw a ReferenceError
. I don't know for sure which is better, but the exception might be preferable. (Can't imagine why using b
before assignment could be useful...)
This is a larger deviation from current =
behavior, but also seems easier to implement (no hoisting). Eh, I guess we still might need to figure out which block the :=
is in to prevent multiple assignments to the same variable (which is illegal in ES6, so if we want to avoid outputting illegal ES6, need to detect in CS6).
From @edemaine on 2016-12-05 13:52
@DomVinyard There are lots of reasons to support let
in some form, discussed on coffeescript6/discuss#35 in particular. Namely, let
enables the programmer, when desired, to control where your variables are accessible, preventing accidental leakage and re-assignment. See also It’s a Mad, Mad, Mad, Mad World: Scoping in CoffeeScript and JavaScript (for example).
From @rattrayalex on 2016-12-05 16:30
Speaking as the person who originally proposed the :=
operator, I actually agree with @DomVinyard on this one.
I'll try to write up the reasons soon.
From @GeoffreyBooth on 2016-12-05 17:03
@edemaine Yes, the declaration is hoisted away from the assignment to follow the pattern established by =
and var
. See http://coffeescript.org/#lexical-scope. I feel like both should behave the same way for consistency (though obviously the let
declarations would be at the top of the block scope, not the top of the function scope).
From @GeoffreyBooth on 2016-12-05 17:05
@DomVinyard and @rattrayalex, the discussion in coffeescript6/discuss#35 was overwhelmed by bikeshedding of people arguing whether let
or :=
were better. If you want to continue that argument, can you please open a dedicated thread for it? I think it would be more productive to improving this proposal if we could just take it as a given that the implementation will be :=
. Thanks.
From @jashkenas on 2016-12-05 18:58
I agree with @rattrayalex and @DomVinyard — CoffeeScript should try to be as minimalistic and boiled down to the essence as we can make it.
JS now has three types of variable assignment (four, if you count named function declarations). CoffeeScript should have one.
That said, CS2 breaking compatibility might be an excellent opportunity for us to switch over from var
to let
, wholesale — if you guys think it's truly a better choice.
From @vendethiel on 2016-12-05 18:59
👍 for let
. but we need to change some hoisting code.
From @connec on 2016-12-05 19:33
@vendethiel or make a break in those situations. I tried to outline something along those lines in this comment.
From @edemaine on 2016-12-05 19:53
@jashkenas All hoisted variables can probably be switched from var
to let
; that's the experiment of GeoffreyBooth's let branch (which still needs a bit of debugging). The point of the :=
operator is to enable creating variables localized to a given scope, i.e., hoisted only to the containing block. I know this idea has been raised before (it could even be added to CS1, by renaming variables), and you're famous for rejecting it. I'd like to think the outcome will be different this time because of ES6's introduction of let
, so JS now supports variable scopes that are not function-wide. I believe CS should embrace this JS feature, given its many uses and ways it can help a programmer be more safe. I see your point about it increasing the complexity of CS, but I also worry about being too simplifying to the point of losing useful features.
From @YamiOdymel on 2016-12-05 20:05
I think we should have a shorthand for let
since we don't use var
but =
in CoffeeScript.
But as a Golang Developer, :=
sounds more like var
to me.
From @edemaine on 2016-12-05 21:05
@YamiOdymel Reading a little about Golang, it seems Go's var
is semantically equivalent to JavaScript's let
(the scope is the containing block). So the semantics proposed for CS :=
exactly match Go's semantics for :=
.
From @GeoffreyBooth on 2016-12-05 21:26
@jashkenas I guess when you mean we should only use let
, do you mean only use let
and only block scope? My let
branch simply outputs let
wherever var
is output now, which effectively means that these are function-scoped let
s, which we can certainly do if your goal is just to banish the var
keyword from our output. We could even do that and still add :=
for block-scoped let
output.
I would be very hesitant to get rid of function-scoped variables (i.e. what we have now). For example, consider this code:
if error
message = 'Damn!'
else
message = 'Woohoo!'
alert message
which currently becomes:
var message;
if (error) {
message = 'Damn!';
} else {
message = 'Woohoo!';
}
alert(message); // 'Damn!' or 'Woohoo!'
If we have only block-scoping, it would be output as:
if (error) {
let message;
message = 'Damn!';
} else {
let message;
message = 'Woohoo!';
}
alert(message); // Undefined
This can be refactored to still work with only block scoping, by adding message = undefined
or similar on the first line, but I think this illustrates how drastic of a breaking change banishing function scope would be. I think we need both scopes, if we’re adding block scoping at all, hence the two operators.
From @rattrayalex on 2016-12-06 04:14
I agree with @jashkenas that there should be one, and only one, way to declare variables in coffeescript. It's coffeescript.
The example you gave is illustrative; it should be written as:
message = if error
"Damn!"
else
"Woohoo!"
which translates as intended.
A more complex example would require intentional hoisted declaration:
a = b = null
if error
doSomething()
doSomethingElse()
a = "Damn!"
b = "What a bummer..."
else
a = "Woohoo!"
b = "I'm so happy!"
This is the "function scoping" you're looking for; not :=
. It's much more clear, intentional, and simple.
let
should be hoisted only to the top of a block scope to avoid temporal dead zones, not to the top of a function. That's just a misleadingly-declared var
, and removes the value of let
.
The biggest problem with this proposal, of course, is backwards incompatibility and an upgrade path. Fortunately, it should be fairly doable to use the coffeescript compiler to write an upgrade tool that checks for any differences between function-hoisted and block-hoisted variables and either warns the developer of each instance or directly inserts varname = null
in the original source, allowing them to audit thereafter.
From @rattrayalex on 2016-12-06 04:30
To expand upon why an operator like :=
would be harmful for this feature...
Imagine you have some code like this:
bar = ->
for i in arr
x := i * 2
if i > 3
x = 3
foo(x)
If you then later decide you don't need x := i * 2
anymore, your code will look like this:
bar = ->
for i in arr
if i > 3
x = 3
foo(x)
and x
is, all of a sudden, a function-level variable leaking outside the for
block. In a world where this kind of bug introduction is possible, you'll constantly need to check every assignment for whether it is first declared with :=
or not, and whenever you remove a :=
, you'll need to check for all possible assignments to the variable.
(In case you're wondering why I proposed :=
for const
given the above, const
is not vulnerable to the above bug; you can't reassign later. That said, I support the decision not to include const
in coffeescript 😄 )
More generally, if we give users the option between function-level and block-level scoping, they're going to have to think about it all the damn time. And since block-level scoping is a best-practice, but not strictly necessary in most cases, developers will constantly find themselves saying, "bah, is it worth adding this extra operator here?". Inconsistency is likely to result.
On the other hand, with a consistent rule of "all variables have block-level scope and can be reassigned", there is a simple calculation, and the handful of times that variables should belong to a function scope, they can be easily declared as noted above, with varName = null
.
From @GeoffreyBooth on 2016-12-06 05:41
I think removing function scoping is a huge breaking change with little benefit. It’s probably better to do nothing than to redefine =
to be always block scoped.
From @rattrayalex on 2016-12-06 07:29
Do we all agree that the two best options are:
var
or let
let
?
From @triskweline on 2016-12-06 08:39
@jashkenas:
JS now has three types of variable assignment (four, if you count named function declarations). CoffeeScript should have one.
The distinction between let
and var
is meaningful: It lets us not accidentally re-assign variables.
We should make CoffeeScript as simple as possible, but not simpler. I believe this is too simple. Please reconsider.
From @connec on 2016-12-06 09:31
I want moving wholesale to let
and block scope to be a good plan, but compared to finding a function (->
or =>
or class
), the lack of explicit braces makes it much harder to identify where a scope begins.
->
# Currently it is clear this is all in a single scope, but if we move to block scope...
if true
a = 1 # Is this block-scoped? Yes.
while true
a = 1 # Is this block-scoped? Yes.
if a = 1
a # Is this block-scoped? No (unless we special-case assigns in `if` condition).
for a in array
a # Is this block-scoped? Yes.
while a = array.shift()
a # Is this block-scoped? No (unless we special-case assigns in `while` condition).
obj =
foo: a = 1 # Is this block-scoped? No (unless we generate a block).
f \
a = 1 # Is this block-scoped? No.
Even if we add a let
or :=
operator this wouldn't entirely go away, especially given CS' "everything is an expression" policy.
if a := 1
a # Where is `a` bound? `if (let a ...)` is invalid JS.
a for a := in array # Where is `a` bound? `for (let a ...)` is _valid_ JS.
# Also not sure what this should look like with `:=` which is important
# given it's one of the most useful cases of `let`.
From @rattrayalex on 2016-12-06 09:56
Hmm. That is somewhat concerning, though the only "surprising" cases seem like antipatterns, and would be likely to give a curious programmer a moment of pause at the very least...
@jashkenas thoughts?
From @connec on 2016-12-06 12:45
I agree they are largely anti-patterns (assignment in expressions in particular), I guess the issue that distinguishing a block from a continuing expression, or determining when the block starts (for
vs. if
) might not always be trivial.
From @GeoffreyBooth on 2016-12-06 15:13
@rattrayalex yalex No, I don't think it's down to just those two choices. The third choice is to keep =
as it is and add :=
, and therefore have both block and function scoping.
From @edemaine on 2016-12-06 15:35
@connec The original proposal makes the choices for :=
pretty clear: the let
goes in the nearest containing block. So if a := 1
compiles to let a; if a = 1
, and a for a := in array
(or however we figure out how to write it) compiles to for(let a in array) a
. Also while a := array.shift() ...
compiles to while(let a = array.shift()) ...
(I believe while
loop iterations get their own block, just like for
loops). In general, we would follow the blocks defined by ES6.
Also, strong preference for keeping =
like it is. I think many underestimate the huge amount of code this would break -- also much harder for beginners to learn (cf. Python, which follows CS =
within a function).
From @jashkenas on 2016-12-06 16:17
I'm in fairly complete agreement with @rattrayalex in this thread. Especially this comment: https://github.com/coffeescript6/discuss/issues/58#issuecomment-265056144
My thoughts:
For starters, this isn't really a giant deal. We've lived with function scope for many years in JavaScript, and although it's not ideal — once you're used to it — it's not really an annoyance in day-to-day life, ever. It becomes normal.
Using let
, but auto-declaring it at the top of functions is pointless. Or to be more colorful, it's an abomination — a perversion of let
's whole point.
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.
If y'all think that block scoping is inherently superior to function scoping, (I too feel that way, but only lukewarmly), then the time to make the breaking change is now. CS2 is the only big breaking change we've had in 6 years — so do it now, or don't do it at all.
From @DomVinyard on 2016-12-06 16:37
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.
Amen.
If y'all think that block scoping is inherently superior to function scoping, then the time to make the breaking change is now.
+1
From @GeoffreyBooth on 2016-12-06 16:39
There is perhaps another option, that might please everyone: detect whether a variable is used only in its block, and if it is, declare it with let
in that block; or else declare it with var
at the top of its function scope like we do now.
if error
time = Date.now()
message = "Error at #{time}!"
else
message = 'Woohoo!'
alert message
Becomes:
var message;
if (error) {
let time;
time = Date.now();
message = `Error at ${time}!`;
} else {
message = 'Woohoo!';
}
alert(message);
There is still only one way to declare variables in CoffeeScript, but we get the benefits of block and function scoping. This way, people who use variables like i
and write lots of code in single files—the people most vocal about the shadowed variable problem—get their block scoped variables, without the need for a new operator or removing function scope.
From @jashkenas on 2016-12-06 16:47
There is perhaps another option, that might please everyone: detect whether a variable is used only in its block, and if it is, declare it with let in that block; or else declare it with var at the top of its function scope like we do now.
Nope. Not quite.
Think it through and you'll see why that doesn't work. If a variable is only used within an inner block, then it makes no difference if it's declared with a var
or a let
— both produce an identical result. And as soon as you mention it outside of the block, it becomes a var
.
This proposal is just a complicated implementation of var
scope.
Edit: I didn't think through @GeoffreyBooth's full proposal — see below.
From @GeoffreyBooth on 2016-12-06 17:28
And as soon as you mention it outside of the block, it becomes a
var
.
I meant only when it’s used in a parent block scope does it become a var
. In other words:
if error
time = Date.now()
message = "Error at #{time}!"
alert message # "Error at 123456789!"
if error
console.log time # undefined
var message;
if (error) {
let time;
time = Date.now();
message = `Error at ${time}!`;
}
alert(message);
if (error) {
console.log(time);
}
time
is used in its block scope, but not in any ancestor block scopes; so it gets defined with let
. message
is used both in its block scope and in its parent block scope, so it gets defined with var
.
From @jashkenas on 2016-12-06 17:50
@GeoffreyBooth — What you're suggesting is pretty similar to how I'd imagine the switch to full let
working:
Right now, CoffeeScript declares a variable in the closest possible function scope to its assignment, with var
.
If we switch, CoffeeScript should declare a variable in the closest possible block scope, with let
.
If a variable is assigned (not just "used" — we have never, and should not, be doing this based on use, only assignment) in a parent block scope it can simply be let
in that higher parent scope. It doesn't need to switch over to var
.
My hypothetical version of your above would be:
message = null
if error
time = Date.now()
message = "Error at #{time}!"
alert message # "Error at 123456789!"
if error
console.log time # undefined
let message;
message = null;
if (error) {
let time;
time = Date.now();
message = `Error at ${time}!`;
}
alert(message);
if (error) {
console.log(time);
}
From @GeoffreyBooth on 2016-12-06 18:04
@jashkenas Yes, that’s how a switch to “only block scoping” would work. My point was that I think there’s a middle ground, where we can kind of “automatically” block scope in the cases where people most want block scoping (temporary variables used only within their block scope) without getting rid of automatically function-scoped variables.
I think getting rid of automatically function-scoped variables is too drastic of a breaking change. Many of the CoffeeScript examples and tutorials across the Web would no longer work.
From @edemaine on 2016-12-06 19:08
Oh, this is a neat idea, @GeoffreyBooth . An example in your new proposal:
unless quiet
if good
message = 'Error'
else
message = 'OK'
alert message
compiles to
if(!quiet) {
let message;
if(good) {
message = "Error";
} else {
message = "OK";
}
alert(message);
}
But we get the benefits for looping:
for i in [1..5]
setTimeout (-> console.log i), i*100
compiles to
for(let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i) }, i*100);
}
This is a difference from CS1: it correctly outputs 1 through 5 wheras CS1 would output 6 repeatedly.
But we need to be a bit careful, as
for i in [1..5]
setTimeout (-> console.log i), i*100
for i in [1..5]
setTimeout (-> console.log i), i*100
becomes
let i;
for(i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i) }, i*100);
}
for(i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i) }, i*100);
}
which outputs 6 ten times. In other words, we still need to be careful that CS hates shadowing, so identically named variables tend to become identical, so you have to be careful when using variables of the same name. (So this doesn't address the "accidental reassignment" issue that let
was helping us avoid.) But I do feel like this is a net improvement from CS1, we "support let" at some real level (not just replacing var
with function-level let
). Given a restriction to only one form of assignment in CS, this is probably my preferred one.
From @jashkenas on 2016-12-06 19:13
My point was that I think there’s a middle ground, where we can kind of “automatically” block scope in the cases where people most want block scoping (temporary variables used only within their block scope) without getting rid of automatically function-scoped variables.
There isn't a middle ground. In your example, alert(message)
would be undefined
. We can't track mentions as determining scope — only assignments. Otherwise you clobber any potential message
deriving from surrounding scopes or the global environment.
in the cases where people most want block scoping (temporary variables used only within their block scope)
If the temporary variables are truly only used (assigned and referenced) within a block scope, then it makes no difference. let
and var
are identical for that case.
I think getting rid of automatically function-scoped variables is too drastic of a breaking change.
I probably agree with you. The smartest course of action is probably to leave things working as they currently are — even though switching to let
would be a slight overall improvement.
From @edemaine on 2016-12-06 19:18
@jashkenas I think the "middle ground" proposal was the following: if x
is assigned within a function (and not in any parent function), then put the let x
declaration in the smallest scope that contains all references to x
(reads and writes) within the function. This is not quite identical to var
, specifically when variables get introduced in loops, as in the setTimeout
example above. I think they are identical in all nonloop cases, though -- the difference arises from the magic that each loop iteration gets its own block in ES6.
From @edemaine on 2016-12-06 19:24
Ah, here is an example where the variable is not introduced in the loop, but still the placement of let
makes a difference:
for i in [1..5]
j = i*2
setTimeout (-> console.log j), i*100
for i in [1..5]
k = i**2
setTimeout (-> console.log k), i*100
would, under the "middle ground" proposal, compile to
let i;
for(i = 1; i <= 5; i++) {
let j;
j = i*2;
setTimeout(function() { console.log(j) }, i*100);
}
for(i = 1; i <= 5; i++) {
let k;
k = Math.pow(i, 2);
setTimeout(function() { console.log(k) }, i*100);
}
In my opinion, this is much more intuitive behavior than CS1, which would output the same value in each loop iteration. The difference can happen whenever loops and closures are combined.
From @GeoffreyBooth on 2016-12-06 19:25
@edemaine I think the block starts after the for
. So for i in arr
would put a let i;
inside the for
loop’s block.
From @edemaine on 2016-12-06 19:30
@GeoffreyBooth Yep. It's particularly clear in CoffeeScript, as for
loops already copy the loop variable within the loop.
for i in array
setTimeout (-> console.log(i)), i*100
could become
let j, len;
for(j = 0, len = array.length; j < len; j++) {
let i;
i = array[j];
setTimeout(function() { console.log(i) }, i*100);
}
whereas currently CS generates
var/let i, j, len;
for(j = 0, len = array.length; j < len; j++) {
i = array[j];
setTimeout(function() { console.log(i) }, i*100);
}
The only difference is the placement of let i
, but the difference is critical.
From @GeoffreyBooth on 2016-12-06 19:32
We can't track mentions as determining scope — only assignments. Otherwise you clobber any potential
message
deriving from surrounding scopes or the global environment.
@jashkenas I was proposing we track mentions of assigned variables to determine scope. Mentions wouldn’t trigger a declaration unless the variable is also assigned in our code. My example doesn’t declare error
, even though it’s mentioned.
Getting back to the original :=
proposal, it seems there’s a consensus that we won’t be adding it? So I’ll close this issue.
I’ll open a new issue for my ”automatically block scope whenever possible” proposal. Maybe @jashkenas is correct and there really isn’t any way to make it workable. I haven’t thought it through enough to know for sure. But in my mind, the options are either that, or leaving things as is, because I don’t think scope is such a huge problem right now to justify the major breaking change that would entail from replacing function scope with block scope exclusively. So hopefully we can find a way to make the “automatically block scope whenever possible” work 😄
From @jashkenas on 2016-12-06 19:39
So hopefully we can find a way to make the “automatically block scope whenever possible” work 😄
You won't. But good luck trying! ;)
But in my mind, the options are either that, or leaving things as is, because I don’t think scope is such a huge problem right now to justify the major breaking change
I think that I agree that leaving things as they are is the best option. @rattrayalex — do you agree as well?
From @aleclarson on 2016-12-07 01:53
@jashkenas Without an explicit let
keyword or :=
operator, it's not possible to override existing variables with a block-scoped variable of the same name. Is it considered bad practice to need such a thing?
x = 0
if y > 0
let x = 1
console.log x # => 1
console.log x # => 0
I agree with @GeoffreyBooth that changing =
to block-scoping is a little extreme. I prefer using let
or :=
to limit variable scope, rather than using foo = null
to hoist variable scope. Having both function and block scoping isn't as crippling as you make it seem, since you only use block scoping when it's obvious to do so.
From @rattrayalex on 2016-12-07 02:02
We haven't thought much as a group about a tool-assisted upgrade path. But I don't really expect such a thought experiment to be encouraging.
So I agree that we should keep scope as-is, function-level only.
In which case, compiling to anything other than var
, ever, is a bit
misleading.
I also agree this thread can be closed. Glad we thought this through!
On Wed, Dec 7, 2016, 07:23 Alec Larson notifications@github.com wrote:
@jashkenas https://github.com/jashkenas Without an explicit let keyword or := operator, it's not possible to override existing variables with a block-scoped variable of the same name. Is it considered bad practice to need such a thing?
x = 0if y > 0 let x = 1 console.log x # => 1console.log x # => 0
I agree with @GeoffreyBooth https://github.com/GeoffreyBooth that changing = to block-scoping is a little extreme. I prefer using let or := to limit variable scope, rather than using foo = null to hoist variable scope. Having both function and block scoping isn't as crippling as you make it seem, since you only use block scoping when it's obvious to do so.
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/coffeescript6/discuss/issues/58#issuecomment-265332131, or mute the thread https://github.com/notifications/unsubscribe-auth/AAq_Lie2CLhSB-ooCGmwo6_12-t3TzPBks5rFhGOgaJpZM4LD7Gz .
From @GeoffreyBooth on 2016-12-07 04:32
I went back and reread the threads that preceded this one. I was looking for examples of problems that would be solved by giving people some way to declare block-scoped variables. The only one I found was from this comment:
window.myModule = do ->
data = -> # ...
# many lines of code
func = ->
# author wants to store stuff in a temporary variable called "data",
# but inadvertently overrides the "data" function above.
data = getSomeData()
data: data
func: func
This is the “shadowing variables” problem, a.k.a. accidentally clobbering variables declared in an outer/ancestor scope. This has been a primary complaint against CoffeeScript since the beginning. Here’s a great essay arguing why it is bad.
Automatically block-scoping variables that aren’t used in their ancestor scopes wouldn’t solve this problem. In fact, I can’t think of any problems it would solve, other than creating more idiomatic ES2015 output (which is a worthy goal in and of itself, but only if there are no breaking changes) and maybe providing some very incremental performance improvements. I can still write up a proposal for it if people want, if anyone can think of a genuine problem it would solve for them.
I think CoffeeScript’s place in the marketplace is threatened enough already that we can’t afford a split like Python 2/3, where many people never upgrade because the breaking changes are too drastic. Linters and tutorials and Stack Overflow examples and all the other components of the ecosystem aren’t maintained enough anymore with enough vigor to all be updated to reflect major breaking changes to core features. If we add block scoping, it needs to be in a backward-compatible way.
So I think our only options are these:
:=
as per the original proposal at the top of this thread, and finally give people block-scoped variables without breaking backward compatibility. (It could even go in 1.x.) I very much acknowledge that this introduces complexity into a language that prides itself on simplicity, and that alone might be reason why :=
isn’t worth adding.From @edemaine on 2016-12-07 14:26
I do think your intermediate proposal of automatic let scoping helps substantially with closures in loops: often you won't need do
anymore, and when you do, a simple assignment would suffice. I'm not sure whether this improvement is worth breaking some old code that relies on the current shadowy behavior though. (Maybe worth running some tests.)
And it certainly doesn't address the issue of programmers taking control of scopes. I agree that this lack of functionality is the main complaint against CS I've seen. (In fact, I was trying to convince a friend to switch from Python to CS, as I've done with other friends, but he couldn't get past this issue.) So I remain in favor of :=
.
From @JavascriptIsMagic on 2016-12-07 18:43
I personally have wished I could use const
in some form in coffeescript so I can program in a pure functional style. The best I can do is pretend to, but I have had a few accidental scope collisions that where hard to track down in larger cs files, which has lead to me making lots of files and breaking up the code significantly more, which is a good thing I suppose.
However you implement it let
and const
keywords I like the best because it's closer to the way you would write a spoken language, and closer to javascript syntax, but I am also fine with something like :=
.
Perhaps :=
for let
and ::=
for const
, (though that might conflict with ::
and .prototype
)
I am seeing const
more and more in modern javascript code even though let
is available, and the reason is mostly so you can guarantee you won't have unintentional scope collision or you get an error, which is nice to catch really early in development/unit testing. Also using import Something from 'something'
will make Something
a const
already in coffeescript.
Those that enjoy pure functional programming
might be attracted to CS's syntax, but be deterred by the lack of const
which javascript has.
Whatever the case I agree that changing the meaning of =
would be a bad thing at this point.
From @JavascriptIsMagic on 2016-12-07 19:09
I would also like to point out that let
and const
are reserved words
already in coffeescript, so using them here should not break any backwards compatibility, right?
From @GeoffreyBooth on 2016-12-09 07:50
@JavascriptIsMagic neither let
/const
nor :=
would break backward compatibility. Changing how =
behaves would. Please see the note about bikeshedding in the original proposal at the top of this thread.
From @JavascriptIsMagic on 2016-12-09 16:53
@JavascriptIsMagic neither
let
/const
nor:=
would break backward compatibility. Changing how = behaves would. Please see the note about bikeshedding in the original proposal at the top of this thread.
Sorry about that, my thought process was that if both Javascript's const
and let
where to be implemented in Coffeescript that we would need 2 different operators or keywords. I am assuming we do not want to change =
for backwards comparability reasons, and I was thinking of a way to better represent two additional ways of assignment, so var
, let
, and const
ES features where represented in some way, whatever operators or keywords are ultimately picked, or what they look like.
edit:
Strictly speaking let
and const
are not necessary as is, you can make a do ->
to block your scope and be careful about scope bleed. However let
and const
are both expected features of Javascript that are implemented in browsers today.
So to match :=
the only other operator I can think of is :==
for const
(perhaps the resemblance to === is fine.)
a ::= b
already means var a.prototype = b
I am mostly advocating for const
here in some form.
Sorry if this is off-topic and belongs in a const
as it's own feature thread.
From @mitar on 2016-12-11 19:45
I also thinks that :=
is a bad idea. I would be OK of changing variable semantics to blocks by default, requiring somebody to declare a = null
before if they want to declare it outside. I think that for thinks like const
I would go with support for TypeScript or Flow annotations and we can declare types through that and maybe then if a type is saying that something is constant, output JavaScript is const
instead of let
or var
. But that should be an optional type annotation on the variable. But there should be only one way to declare a variable.
From @GeoffreyBooth on 2016-12-12 00:47
I don’t think we should be considering switching all variable assignment to block scoping. Just like automatic inferred block-scoped variables don’t solve the accidental-clobbering problem, neither would all-block-scoped variable assignment. Consider the example from above:
window.myModule = do ->
data = -> # ...
func = ->
data = getSomeData()
Even if we did away with function scope completely and always declared variables via let
in block scope, you still have the second data
clobbering the first here, as the second one is still inside the first one’s block. If we’re not going to solve this problem, there’s no point in changing things.
From @connec on 2016-12-12 11:44
If we’re not going to solve this problem, there’s no point in changing things.
I always felt the the main advantage of let
was for improving the scope of loop variables:
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i))
}
The equivalent in CS would currently require a (relatively expensive, I expect) IIFE and some variable duplication:
for i in [0...10] then do (i) ->
setTimeout -> console.log i
# Gets much worse if you have several parameters
for { id, name, params } in results then do (id, name, params) ->
doSomethingAsync id, name, params
Perhaps we could have a small syntactical addition for this case exclusively, e.g for let
for let { id, name, params } in results
doSomethingAsync id, name, params
From @aleclarson on 2016-12-12 12:27
@connec Maybe loops should always use let
? Then we could reuse variable names across loops sharing a function scope. Although, this would break any code accessing loop variables after the loop finishes.
From @GeoffreyBooth on 2016-12-05 07:21
Splitting off from coffeescript6/discuss#35, this proposal is for just one new operator:
:=
, the block assignment operator. So the great advantage oflet
/const
is its block-scoping, i.e.:This is a genuinely new feature added in ES2015, and a great improvement over
var
, which explains whylet
andconst
have become popular features. This block scoping thatlet
andconst
each offer is probably something that CoffeeScript should have, separate from the feature ofconst
that means “throw an error on reassignment.”The new operator would behave similarly to
=
:a = 1
means, “declarea
at the top of this function scope usingvar
, and assigna
here with the value of1
.”a := 1
would mean, “declarea
at the top of this block scope usinglet
, and assigna
here with the value of1
.”let
has the same issue asvar
, in that its declaration is hoisted to its entire scope (what MDN refers to as the temporal dead zone), solet
declarations should be grouped together at the top of their block scope similar to howvar
declarations are currently grouped at the top of their function scope.What about
const
? Well, there’s really no reason we need it. It protects against reassignment of variables, and that’s all it does. Per this comment, it probably has no performance benefit in runtimes; and sinceconst
reassignments can be caught by transpilers (whether CoffeeScript or Babel) such reassignments are in practice usually caught at compile time, not run time. If we decide we want to provide a way to outputconst
, for full expressibility of ES2015 and for this protection against reassignment, that could be a separate feature added later.So this CoffeeScript:
would compile into this JavaScript:
Note about bikeshedding: Please refrain from feedback that says how you prefer the
let
keyword to:=
, or that you prefer some other sequence of characters to:=
. Ultimately the keyword or operator chosen is a personal preference decision, and will be made by @jashkenas and whoever implements this. There was plenty of discussion about this on coffeescript6/discuss#35. Thanks.