-
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 operator #4951
Comments
From @DomVinyard on 2016-12-05 13:42 Strongly dislike Everything is |
From @edemaine on 2016-12-05 13:47 @GeoffreyBooth You've probably thought about this, but why are you hoisting the
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. This is a larger deviation from current |
From @edemaine on 2016-12-05 13:52 @DomVinyard There are lots of reasons to support |
From @rattrayalex on 2016-12-05 16:30 Speaking as the person who originally proposed the 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 |
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 |
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 |
From @vendethiel on 2016-12-05 18:59 👍 for |
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 |
From @YamiOdymel on 2016-12-05 20:05 I think we should have a shorthand for But as a Golang Developer, |
From @edemaine on 2016-12-05 21:05 @YamiOdymel Reading a little about Golang, it seems Go's |
From @GeoffreyBooth on 2016-12-05 21:26 @jashkenas I guess when you mean we should only use 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 |
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
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 |
From @rattrayalex on 2016-12-06 04:30 To expand upon why an operator like 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 bar = ->
for i in arr
if i > 3
x = 3
foo(x) and (In case you're wondering why I proposed 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 |
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 |
From @rattrayalex on 2016-12-06 07:29 Do we all agree that the two best options are:
? |
From @triskweline on 2016-12-06 08:39
The distinction between 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 ->
# 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 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 ( |
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 |
From @edemaine on 2016-12-06 15:35 @connec The original proposal makes the choices for Also, strong preference for keeping |
From @jashkenas on 2016-12-06 16:17 I'm in fairly complete agreement with @rattrayalex in this thread. Especially this comment: coffeescript6/discuss#58 (comment) My thoughts:
|
From @DomVinyard on 2016-12-06 16:37
Amen.
+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 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 |
From @jashkenas on 2016-12-06 16:47
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 This proposal is just a complicated implementation of Edit: I didn't think through @GeoffreyBooth's full proposal — see below. |
From @GeoffreyBooth on 2016-12-06 17:28
I meant only when it’s used in a parent block scope does it become a 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);
}
|
From @GeoffreyBooth on 2016-12-06 19:32
@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 Getting back to the original 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
You won't. But good luck trying! ;)
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 x = 0
if y > 0
let x = 1
console.log x # => 1
console.log x # => 0 I agree with @GeoffreyBooth that changing |
From @rattrayalex on 2016-12-07 02:02 We haven't thought much as a group about a tool-assisted upgrade path. But So I agree that we should keep scope as-is, function-level only. In which case, compiling to anything other than I also agree this thread can be closed. Glad we thought this through! On Wed, Dec 7, 2016, 07:23 Alec Larson [email protected] wrote:
|
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:
|
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 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 However you implement it Perhaps I am seeing Those that enjoy pure Whatever the case I agree that changing the meaning of |
From @JavascriptIsMagic on 2016-12-07 19:09 I would also like to point out that |
From @GeoffreyBooth on 2016-12-09 07:50 @JavascriptIsMagic neither |
From @JavascriptIsMagic on 2016-12-09 16:53
Sorry about that, my thought process was that if both Javascript's edit: So to match I am mostly advocating for |
From @mitar on 2016-12-11 19:45 I also thinks that |
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 |
From @connec on 2016-12-12 11:44
I always felt the the main advantage of 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 { id, name, params } in results
doSomethingAsync id, name, params |
From @aleclarson on 2016-12-12 12:27 @connec Maybe loops should always use |
From @edemaine on 2016-12-12 13:24 @aleclarson If we automatically declare every variable at the nearest scope where it gets used (the intermediate proposal), then we won't break code that uses the for loop variable afterwards, but we will still make it easy to do closures within for loops (just don't use the same variable outside). |
From @connec on 2016-12-12 14:23 I think the idea of choosing variable scope based on usage has been discarded, e.g. i for i in array
i # is this trying to bring `i` above into the function scope, or is it referencing
# some other `i` (perhaps even a global)? Always using i = null
i for i in array
i # all `i`s are the same reference The problem then is that we have two types of implicit scope, compounding the uncertainty about what variable are declared where. i for i in array
i # was `i` above already used somewhere in this scope, and so is available? Or is it
# unassigned here? Having an explicit syntactic modifier would eliminate the guesswork: i = null
i for let i in array
i # `i is null`, since the `i` in the loop was specified with `let` That said, if one block scoping thing gets in, there will probably be a push for others, and my preference would be to have "all function scope" rather than adding scope switches that can be used anywhere. Just in this particular case I think it makes sense to add a syntax for what is a fairly common ask from people regarding loops (and even a source of bugs). Particularly when the only supported solution (wrap it in a closure), is likely to be quite expensive in comparison.citation needed |
From @DomVinyard on 2016-12-12 16:15 The general consensus for this particular issue seems to be trending towards "leave things as they are" with a view to either block-scoping by default at some point in the future, or scope based on usage at some point. As for now, no action? |
From @GeoffreyBooth on 2016-12-12 17:29 I think compared with changing how scope works in the language overall, no action. We need function scope. But I still think we should discuss the merits of this proposal—the original one at the top of this thread. It doesn’t break backward compatibility, it finally adds block scoping to CoffeeScript, and it solves the “clobbering variable” problem. It does introduce a second way of declaring a variable. I think we’ve determined that there’s no silver bullet solution to have two types of scopes (function and block) in CoffeeScript without two ways to declare variables. (At least, not for the reasons that people want both scopes.) So I guess the question is whether we value the simplicity of a single way to declare variables over the power of having two types of scopes. Part of that consideration should include how popular |
From @edemaine on 2016-12-12 18:33 @GeoffreyBooth If it's alright with you, I'd like to try writing a new issue proposing changing the normal
I think this proposal is somewhat independent from the |
From @GeoffreyBooth on 2016-12-12 18:39 Sure @edemaine, please create a new issue and link to this one. As far as I can tell though it doesn’t give people what they really want. They want block scope because they want to use it to protect them from themselves: not accidentally clobbering parent-scope variables, etc. We can have the compiler automatically declare variables as block scoped (or even as It’s perhaps better than doing nothing, as our output would be more idiomatic ES and might be very slightly more performant, if fewer variables get declared overall; but those are the only gains I can see. Please feel free to prove me wrong 😄 |
From @edemaine on 2016-12-12 19:45 OK, the intermediate proposal is now in coffeescript6/discuss#62. Hopefully that explains why it's useful, as well as the limitations you describe. It does not give the programmer control to prevent accidental lexical scoping problems (just like current CS). One alternative approach for that, though, is in another new (but modest) proposal, coffeescript6/discuss#61. |
From @DomVinyard on 2016-12-13 10:01
This is fundamental. It might be worth polling people directly on. To me, a single way to declare variables is one of CS's most powerful features. It should not be discarded lightly. Not sure how many others feel like that, perhaps i'm an outlier. |
From @triskweline on 2016-12-13 11:17
To me it's a footgun that has cost me countless hours to debug. I might also be an outlier, but this issue must affect any program with long or nested closures. Note that some form of window.myModule = do ->
const data = -> # ...
const func = ->
data = getSomeData() # throws compiler error If we cannot agree on how to scope, can we maybe agree on having |
From @mitar on 2016-12-13 11:24 I think that static-typing features like const should be part of the general type-checking optional addition on top of core CoffeeScript language. |
From @JavascriptIsMagic on 2016-12-13 12:05 @DomVinyard At the time I see ( And looking at the progress so far in the last few months If you where to ask me, I would say that My concern is that leaving @henning-koch I've shot myself in the foot so many times that now whenever I declare a variable, I do a "find" in my editor for that variable name, and scan the file to make sure I haven't used it already, and I also try to keep my names as unique as possible because I can't be too careful. Sometimes I use
I won't argue for things like all variables should be |
From @mrmowgli on 2016-12-13 13:04 Personally I would be perfectly happy allowing the const/let keywords in CS6, and making sure it was optional only. I don't find the current version of CS causing me those kinds of issues, but I can see the value. There are many times when I really want a pass through, where CS syntax would let me use ES2015+ constructs like get /set or const and just pass those through. In other words a lot of the modifiers aren't currently supported, but I think they will end up getting added. I personally don't need special symbols but it would be nice to be able to pass through a const or a get now and then. That being said, for now it's medium priority and also covered in several other threads, like classes. Take the time to read up on the issues, and potentially start looking at the code for for coffeescript and put together a pull request :) |
From @connec on 2016-12-13 16:18
I agree with this statement.
Whilst there are 4+ syntaxes to declare variables, the variable is declared in the same way every time (with some additional acrobatics for |
From @GeoffreyBooth on 2017-11-25 08:26 Closing as the consensus for CoffeeScript 2 was “no action.” It seems like there might be a consensus in a theoretical CoffeeScript 3 that we shift from the current function scoped variables to always block scoped variables. That should get its own thread though. |
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.The text was updated successfully, but these errors were encountered: