-
Notifications
You must be signed in to change notification settings - Fork 4
CS2 Discussion: Features: Block assignment operator #58
Comments
Strongly dislike Everything is |
@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 |
@DomVinyard There are lots of reasons to support |
Speaking as the person who originally proposed the I'll try to write up the reasons soon. |
@edemaine Yes, the declaration is hoisted away from the assignment to follow the pattern established by |
@DomVinyard and @rattrayalex, the discussion in #35 was overwhelmed by bikeshedding of people arguing whether |
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 |
👍 for |
@vendethiel or make a break in those situations. I tried to outline something along those lines in this comment. |
@jashkenas All hoisted variables can probably be switched from |
I think we should have a shorthand for But as a Golang Developer, |
@YamiOdymel Reading a little about Golang, it seems Go's |
@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 |
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 |
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 |
I think removing function scoping is a huge breaking change with little benefit. It’s probably better to do nothing than to redefine |
Do we all agree that the two best options are:
? |
The distinction between We should make CoffeeScript as simple as possible, but not simpler. I believe this is too simple. Please reconsider. |
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`. |
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?
…On Tue, Dec 6, 2016 at 1:31 PM Chris Connelly ***@***.***> wrote:
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.
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 assignments 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 assignments 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.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#58 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAq_LomXcQQI6vdp2EDc-8Q8A9-k0H03ks5rFSuFgaJpZM4LD7Gz>
.
|
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 ( |
@rattrayalex yalex No, I don't think it's down to just those two choices. The third choice is to keep |
@connec The original proposal makes the choices for Also, strong preference for keeping |
I'm in fairly complete agreement with @rattrayalex in this thread. Especially this comment: #58 (comment) My thoughts:
|
Amen.
+1 |
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 |
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. |
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);
}
|
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 |
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? |
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 |
@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 |
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 😄 |
OK, the intermediate proposal is now in #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, #61. |
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. |
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 |
I think that static-typing features like const should be part of the general type-checking optional addition on top of core CoffeeScript language. |
@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 |
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 :) |
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 |
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. |
Migrated to jashkenas/coffeescript#4951 |
Splitting off from #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 #35. Thanks.The text was updated successfully, but these errors were encountered: