-
Notifications
You must be signed in to change notification settings - Fork 2k
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
CS2 Discussion: Features: Block assignment let and const operators #4931
Comments
From @kirly-af on 2016-09-15 17:14 Hi Geoffrey, thanks for starting this discussion. if true
foo = 42
console.log foo # 42, expected undefined To solve the hoisting issue we could just transpile to The second issue it solves is shadowing: foo = 42
unless bar?
foo = 21
console.log foo # 21, expected 42 In this case, we need a way to shadow the inner block declarations. Maybe we could opt for something like the suggested I'd prefer something like the non-standard JS let expressions: foo = 42
if true
let (foo = 'a', bar = 'a') console.log foo, bar # a b
console.log foo # 42 There is also a non standard foo = 42
if true
let (foo = 'foo', bar = 'bar')
console.log foo
console.log bar
console.log foo As far as |
From @GeoffreyBooth on 2016-09-16 15:12 @kirly-af two quick notes: the const obj = {};
obj.a = 42;
obj.a = 43; This is a big reason why |
From @kirly-af on 2016-09-16 16:48 Hi Geoffrey I'm aware let blocks/expressions are non standard ES features. I'm not sure we should care about that, we are free to adopt any construct in CoffeeScript syntax. Anyway it was just a sintax suggestion, my point was to have explicit redeclaration (ie. Concerning Let me know if some part of what I say is not clear, I don't mind elaborate more. |
From @GeoffreyBooth on 2016-09-16 18:27 The uppercase issue has been discussed already, if not here than in coffeescript6/discuss#31 or coffeescript6/discuss#1. There were a lot of objections, because in most people’s ES2015 code most variables are CoffeeScript doesn’t have declarators such as |
From @kirly-af on 2016-09-16 19:18 I agree let/const keywords should be avoided if possible. My main concern is that ES2015 block variables will be completely unintuitive: if true
foo = 'a'
bar := 'b'
console.log foo # 'a'
console.log bar # RefError thrown For this reason, I think we should transpile all declarations to Still, I really think, we should not allow shadowing in a language like CS. Python doesn't have block-level (understand if/while/for/switch) variables, like CS, and it's just fine. Even though I see how useful that feature is, I think it's probably just going to be error-prone. |
From @GeoffreyBooth on 2016-09-16 20:26 I think redefining what |
From @kirly-af on 2016-09-16 20:42 I get your point, and you're completely right. Though I see more this change as a fix. Consider the example I used earlier: if true
foo = 42
console.log foo # 42 To me this is something that shouldn't be work. At least if we use |
From @GeoffreyBooth on 2016-09-16 22:59 Take it up with JavaScript. Until I don’t think code blocks like this are terribly uncommon: if error?
message = 'Error!'
else
message = 'Success!'
alert message Obviously there are more concise ways to write this in CoffeeScript, but imagine the messages were much longer and you can see why someone would write it out across multiple lines. I think code like this is common across many projects, and we can’t cause blocks like this to break. |
From @kirly-af on 2016-09-16 23:21 Humm... yeah, I never did such thing but it's reasonable to think a significant number of users do this (I'm not making any judgement, just I never thought about doing such thing). Still, I'd be in favor to keep it simple, as it is. |
From @lydell on 2016-09-17 09:06 Just throwing a thing in here:
|
From @GeoffreyBooth on 2016-09-17 17:04 Almost that exact example is discussed in the MDN article on This goes back to my point about coffeescript6/discuss#31. I feel like we can either keep things as they are, or somehow allow |
From @GeoffreyBooth on 2016-09-17 18:25 I just did a little experiment where I changed the compiler to output Amazingly, all but two tests still pass. The two that fail involve the REPL: testRepl "variables in scope are preserved", (input, output) ->
input.emitLine 'a = 1'
input.emitLine 'do -> a = 2'
input.emitLine 'a'
eq '2', output.lastWrite()
testRepl "existential assignment of previously declared variable", (input, output) ->
input.emitLine 'a = null'
input.emitLine 'a ?= 42'
eq '42', output.lastWrite() So I guess the REPL would need to be updated to work with These two tests would pass as non-REPL CoffeeScript:
As does the other test. The above compiles to (in let a;
a = 1;
(function() {
return a = 2;
})();
console.log(a); Even the example I posted above, where I thought if error?
message = 'Error!'
else
message = 'Success!'
alert message becomes: let message;
if (typeof error !== "undefined" && error !== null) {
message = 'Error!';
} else {
message = 'Success!';
}
alert(message); Since we’re hoisting the The bigger question is whether we should do this. I’m not sure |
From @triskweline on 2016-10-13 09:57 Thanks @GeoffreyBooth for leading this effort. I wanted to post a counter to the "I rarely used felt the need for let in CS" above. Over at Unpoly Coffeescript's lack of We're heavy users of the revealing module pattern. This very useful pattern lets you write class-like constructs without the constant housekeeping of keeping It looks like this: window.myModule = do ->
privateState = 'foo'
privateFunction = -> # ...
publicFunction1 = -> # ...
publicFunction2 = -> # ...
# return public API
publicFunction1: publicFunction1
publicFunction2: publicFunction2
# Usage of the module
myModule.publicFunction1()
myModule.publicFunction2() What has repeatedly happened is that someone accidentally overrode a function by declaring a variable with the same name. This is caused by CS hoisting all assignments to the topmost scope possible: window.myModule = do ->
data = -> # ...
# many lines emitted
func = ->
# author wants to store stuff in a temporary variable called "data",
# but accidently overrides the "data" function above.
data = getSomeData()
data: data
func: func These kinds of errors are hard to debug, because the I'm hoping some kind of variable scoping can eventually make its way into Coffeescript. |
From @kingdaro on 2016-12-01 23:16 I personally think this is a bigger issue than its priority makes it seem. Variable scoping is a problem CS has had since ever, due to the simple fact that Since both let foo = 1 # let declaration
const bar = 2 # const declaration
if true
baz = 3 # no `let` or `const` of baz exists in scope, assign as a `var`, declared at head of function
let foo = 6 # local foo assignment
bar = 42 # compile-time error?
console.log foo #=> 6
console.log foo #=> 1
console.log baz #=> 3 And maybe for simplicity's sake, leave |
From @aleclarson on 2016-12-02 00:14 I like @just-nobody's idea of...
...for 4 reasons...
As an extra measure to avoid temporal dead zones, console.log foo # undefined, because `var` is used
foo = 1
if error?
console.log foo # undefined, because `let` is hoisted
let foo = 2 ...compiles to... var foo;
console.log(foo);
foo = 1;
if (error != null) {
let foo;
console.log(foo);
foo = 2;
} |
From @GeoffreyBooth on 2016-12-02 00:44 If we add support for As for allowing only |
From @aleclarson on 2016-12-02 01:22 I think the utility of But I contend |
From @kingdaro on 2016-12-02 08:13
I don't really agree with this. I like CS a lot because of its expressiveness and readability, how you can write statements like I understand the reasoning, but I really think the benefits of having a |
From @edemaine on 2016-12-03 17:10 Another context where
and have that compile to something like
whereas currently we need to write
I'm not sure how to express this with an operator like Anyway, supporting |
From @connec on 2016-12-04 02:19
Not particularly relevant to the thread, but for this case, in the meanwhile, you can use for i in [1..5] then do (i) ->
setTimeout (-> console.log(i)), i*100 More relevant to the thread: I also think @jashkenas' comments in #712 still stand regarding lexical scoping being a positive feature of CoffeeScript. Issues with variable shadowing tend to arise frequently when you're doing too much in one lexical context (e.g. writing your whole app/library in a single file, or naively joining files without a safety wrapper). Orthogonally, I think a wholesale change to
Another point is that I can imagine this being quite complicated to implement. The current rules of scope aligning exactly with function boundaries seems fairly heavily exploited by the compiler. Actually, I think it'd be pretty cool to compile to console.log item, i for item, i in items
console.log item while item = items.shift() for (let i$ = 0; i$ < items.length; i$++) {
const i = i$, item = items[i$]
console.log(item, i)
}
let item$
while (item$ = items.shift()) {
const item = item$
console.log(item)
} |
From @GeoffreyBooth on 2016-12-04 04:46 If I can try to keep us on track, I think that if we implement I don’t think we need any more arguments for why we should support So aside from those two things, does anyone have any other feedback on the proposal at the top of this thread? |
From @kingdaro on 2016-12-04 04:53 Yeah, I read @jashkenas ' comments on the issue I linked, and he does have a good point that scope declarations introduce unneeded complexity for end users, and that scope management should ultimately be the responsibility of the programmer, not the language. Regardless, +1 to this proposal, for bringing more parallels from ES6. |
From @edemaine on 2016-12-04 19:03 I agree with this proposal over coffeescript6/discuss#31 (but I'm new here, so don't know how much my vote counts). I definitely have more use for Regarding automatic I see the appeal to an operator approach more and more; my only dissatisfaction is the inability to do something like |
From @jcollum on 2016-12-04 20:26 I like the idea of having both To me the How about:
to
After spending time in React-Native land (and slowly ramping up on Redux) I see the appeal of not being able to reassign variables. There's a preference for symbols over words in here though -- that doesn't seem very coffeescripty. The coffeescript solution seems to be to favor plain words instead of symbols when possible (see
to
I know people want to keep keywords that determine variable scoping out of the language but the alternative is either not doing it (which would become a big -- and valid -- criticism of coffeescript) or using symbols, which seems antithetical to the language itself. |
From @GeoffreyBooth on 2016-12-04 21:29 Again, please just assume that the symbols will be The main open questions in this proposal are:
Currently the proposal states:
If we have only one operatorIf we have only one operator, it would be Assuming we could reliably detect variable reassignment via static analysis, I think we would want to output The argument for having only one operator is that it fits in nicely with CoffeeScript’s current design pattern regarding The other argument in favor of If we have both operatorsIf we have both operators, should The argument for having both operators is that it’s full compatibility with ES2015; people would be able to output But it comes at the cost of users having three operators to understand and use correctly, and the added mental burden of needing to consider “should I ever need to reassign this variable?” when you declare one. Is the ability to protect yourself against accidental reassignment worth adding an additional operator into CoffeeScript? Should we leave that to linters or type checkers? |
From @jcollum on 2016-12-04 21:52 After reading about it wouldn't Since It sounds like the only place Full compatibility with ES2015 is addressed with That all seems fine to me; I withdraw my earlier statement :-) I'm saying I'm in favor of:
becoming:
I'm having a hard time seeing the right place to use |
From @GeoffreyBooth on 2016-12-04 22:09 @jcollum we can’t redefine We can redefine Redefining |
From @jcollum on 2016-12-04 22:14 I see, sorry. In that case the only real use I can see for Thanks for your patience here @GeoffreyBooth. |
From @kingdaro on 2016-12-04 23:31 I actually just came across another solution, which should probably go in its own issue, but I'll mention it here anyway. MoonScript has a x = 5
dostuff = (using nil) ->
x = 10
dostuff!
print x --> 5 Maybe we can pull something like that over into coffeescript? I like the idea of this more than a new assignment operator, honestly. |
From @GeoffreyBooth on 2016-12-05 01:53 @just-nobody MoonScript compiles into Lua, not JavaScript. What JavaScript would your example compile to? I’m not aware of a way to introduce a new scope in JavaScript that doesn’t inherit the variables from its parent scope. (Though if you can prove me wrong about this, please start a new issue. I’d like to keep this issue focused on the proposal in the first comment. Thanks!) |
From @aleclarson on 2016-12-05 02:49 The following scenarios describe how the Click to expand
1: A variable that is never reassigned Assuming the compiler can determine immutability, it's safe to default to This scenario occurs within a function scope (or the global scope). Scenarios 3 and 5 deal with behavior within a block scope. x = 0
# Output:
const x = 0; 2: A variable that is reassigned at least once In this scenario, we keep This scenario occurs within a function scope (or the global scope). Scenarios 3 and 5 deal with behavior within a block scope. x = 0
x += 1
# Output:
let x = 0;
x += 1; 3: Using In this scenario, we cannot use if true
x = 0
y = 0
y += 1
# Output:
let x, y;
if (true) {
x = 0;
y = 0;
y += 1;
} 4: Using This scenario is almost identical to scenarios 1 and 2, but in the context of an As seen below, if true
x := 0
y := 0
y += 1
# Output:
if (true) {
const x = 0;
let y = 0;
y += 1;
} 5: Using This scenario is identical to using while true
x = 0
y = 0
y += 1
# Output:
let x, y;
while (true) {
x = 0;
y = 0;
y += 1;
} 6: Using This scenario is identical to using while true
x := 0
y := 0
y += 1
# Output:
while (true) {
const x = 0;
let y = 0;
y += 1;
}
Anything wrong with this solution? If this solution is used, it's probably worth having a separate discussion about only |
From @kingdaro on 2016-12-05 03:33 |
From @GeoffreyBooth on 2016-12-05 03:53 Over on this thread I asked @alangpierce how they implemented the “output
So I think perhaps we should split this proposal into two features: a “block assignment operator,” |
From @edemaine on 2016-12-05 03:56 @aleclarson That seems mostly right, but your rules 1 and 2 don't really make sense for |
From @edemaine on 2016-12-05 04:03 @GeoffreyBooth Agreed. I think the main argument for |
From @aleclarson on 2016-12-05 04:09 @edemaine I should have clarified that scenarios 1 and 2 are only if used within the global scope or a function scope. As long as that condition is met, using If defaulting to The one nice thing about my solution above: never see a func = (x) ->
if x > 0
y = 0
z := 1
# Outputs:
let func;
func = function(x) {
let y;
if (x > 0) {
let z;
y = 0;
z = 1;
}
}; |
From @GeoffreyBooth on 2016-12-05 07:24 I created coffeescript6/discuss#58 to propose just Anyone opposed to closing this proposal in favor of coffeescript6/discuss#58? |
From @GeoffreyBooth on 2016-09-12 06:46
Building off of coffeescript6/discuss#1 and this comment, the great advantage of
let
/const
is its block-scoping, i.e.:This is a dramatic improvement over
var
, and a big reason whylet
andconst
have become popular features. This block scoping is probably something that CoffeeScript should have, separate from the feature ofconst
that means “throw an error on reassignment.”We could have both, via
:=
and:==
operators (or whatever two operators people think are best):a := 1
would mean, “declarea
at the top of this block usinglet
, and on this line assigna
with the value of1
.”a :== 1
would mean, “declare and assigna
on this line usingconst
. If it gets reassigned later, throw an error.”We don’t necessarily need the second operator, if we don’t care to give the “throw an error on reassignment” feature.
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.const
must be declared and assigned on the same line, so it can’t get moved up.So this CoffeeScript:
would compile into this JavaScript:
The text was updated successfully, but these errors were encountered: