Skip to content
This repository has been archived by the owner on Feb 19, 2018. It is now read-only.

CS2 Discussion: Features: let/const/var #1

Closed
DomVinyard opened this issue Jul 11, 2016 · 88 comments
Closed

CS2 Discussion: Features: let/const/var #1

DomVinyard opened this issue Jul 11, 2016 · 88 comments

Comments

@DomVinyard
Copy link

Personally, I believe these should be untouched. I can see that there is a case for implementing const in almost any language remit. But not coffeescript.

@carlsmith
Copy link

carlsmith commented Jul 12, 2016

Whether we have let and var or not is a different question than whether we have const. The first two are related to scope, and const is about reassignment.

We should keep this discussion about let and var, and move const to a new issue.

Should we even keep CoffeeScript's nonlocal assignment rule? If were making breaking changes, we should consider changing that as it was quite controversial - all assignments are currently nonlocal. The name lookup starts locally, and works outwards until it finds the name and reassigns it. If it doesn't find the name, the assignment is local.

In Python, all assignments are just local, which is what you normally want to happen. Lexical scope exists to keep variables locally defined. You never have to worry about clobbering something in the global scope with a local variable like you do in JS and CoffeeScript.

The problem with Python is that it uses nonlocal statements when you want to reassign names in outer scopes, the way CoffeeScript does by default, and we really want to avoid var and let and any other assignment keywords. They're especially awkward in a language where everything is an expression.

Bikeshedding

I think all assignments should be local, and we should have a variable available inside each function, just like arguments, called outer, that's a reference to the function's outer scope.

You would use outer the same way we use window in the browser to do global assignments:

outer.x = 1

If you had functions nested inside of each other, each lexical scope would have its own outer, so it would naturally be recursive. You could do outer.outer.outer... all the way to the global scope.

Assignments should have always been local, and it's natural to extend the idea behind window to all scopes. Then you don't need assignment keywords, can access any scope, and it works naturally as an expression.

@rattrayalex
Copy link
Contributor

const is now often the default in ES6 code. Making it convenient to use would be a plus. But not necessarily a priority.

My hunch is the outer proposal, while interesting, would be too far a departure to take on right away.

@rattrayalex
Copy link
Contributor

I don't think there's much reason to use let since coffeescript's var's have (most of) the same benefits; they are block-scoped.

If adding a const keyword isn't too hard, it might be worthwhile.

An alternative would be to have all variables by const by default, and allow explicit var or let declarations. I assume this would be more work, as the scoping code coffeescript undertakes would be mostly-removed.

@DomVinyard
Copy link
Author

You could do outer.outer.outer... all the way to the global scope.

That sounds like an absolute nightmare. Hard-coding your depth in the scope.

It will be very easy to reimagine all of coffeescript's features under the mandate of 'allowing breaking changes' - but I think there is a good case for picking a few bits of low hanging fruit and then addressing the rest once we're up and running. let/var/const would be a good candidate for leaving alone, i think.

@carlsmith
Copy link

I don't see a problem with absolute scope references, when scope is already lexical. Still, we have more worthwhile stuff to discuss, so yeah, forget it.

@JimPanic
Copy link
Contributor

I really want to see const in this new endeavour in one way or another. Maybe not in the first iteration, but definitely when targeting ES6. This is a key feature for me (and I guess a lot of other people as well) in ES6.

Regarding scoping, I'm in favour of leaving lexical scope as-is as well. In the future it might be a good idea to make let the default, const easy to use and var the least common thing syntactically.

@carlsmith
Copy link

carlsmith commented Jul 19, 2016

Hi @JimPanic, welcome.

Regarding scoping, I'm in favour of leaving lexical scope as-is as well.

You misunderstood the problem I was trying to address. CoffeeScript will always have lexical scope as-is. No one wants to change that. It's the way CoffeeScript mandates that all assignments are nonlocal that's the issue. It's always been a pretty controversial feature, and one of the most popular criticisms of CoffeeScript. For an example of the feature, here's two seemingly similar blocks of code, the first is CoffeeScript, the second is Python. They do different things though:

    x = false

    do ->
        x = true
        console.log x # true

    console.log x     # true 

In CoffeeScript, the outer x is clobbered by the inner assignment. Whereas, without an explicit nonlocal x statement, Python will never clobber names outside the local namespace.

    x = False

    def f():
        x = True
        print x       # True
    f()

    print x           # False

We can avoid clobbering x in CoffeeScript too, if we introduce the name into the local namespace as a parameter, but you still have to know that that name exists in some outer scope to preempt the clobbering.

    x = false

    do (x) ->
        x = true
        console.log x # true

    console.log x     # false 

Lacking any real evidence, I can just say from experience with Python that nonlocal assignment is pretty rare. You almost always want assignments to be local. I assume @jashkenas only went with nonlocal assignment to avoid having to have declarators. There's no other sane reason to do it.

If we're going to change the language design to introduce declarators, then we should definitely make assignment local by default and have a declarator for nonlocal assignment. We don't need Python's global declarator as we have window.x already.

If let makes a variable block-scoped and const makes the name un-reassignable and block-scoped, neither does what we need.

Even if we keep assignments nonlocal, if we introduce declarators, we would really need declarators for local, const and local const.

The benefit of having block-scope in CoffeeScript is slim. We can always create a new scope with do ->, and any variables in the function will be effectively block-scoped (again, assuming we don't clobber anything). Having let would allow us to do this:

if foo
    let x = true
    let y = false

But, we can already do this anyway:

if foo then do ->
    x = true
    y = false

JavaScript became considerably more complicated by introducing let, and it was only introduced to fix different problems to the ones CoffeeScript has. We don't have much use for it.

I personally think outer.xis better than nonlocal x because it's explicit about which scope it references, and follows logically from window.x. Still, I'm happy to hear other ideas.

Taking declarators from JavaScript requires changing them to fit CoffeeScript's nonlocal assignment feature, which may in turn require any declarators to be renamed for their new semantics, so we'd basically need to define our own declarators from scratch. And we really should try and do everything we can to avoid introducing declarators, given that they have been rejected from CoffeeScript for seven years, and they make everything look like Java.

jQuery ->

    local const square = (x) -> x * x

@JimPanic
Copy link
Contributor

@carlsmith You are right, I did misunderstand.

I always thought CS was favouring the innermost scope in assignments. Lucky me it didn't bite me so far.

@carlsmith
Copy link

@JimPanic :) It's not obvious what CoffeeScript is doing, especially when it seems to be doing what you'd expect, coming from other languages. It doesn't bite that often, but when it does, it can be totally baffling.

@rattrayalex
Copy link
Contributor

rattrayalex commented Jul 20, 2016

Thanks very much @carlsmith for the terrific explanation there. Didn't understand that bit myself so well till now 😄

I do agree that it would likely be too ambitious to change how scoping works, unless we can simplify the coffeescript compiler by staying closer to JS's behavior.

@rattrayalex
Copy link
Contributor

[const] is a key feature for me

@JimPanic can you elaborate? I intuitively feel the same way – I enforce const-by-default in my eslint – but curious to hear if there are additional reasons why const is useful.

@rattrayalex
Copy link
Contributor

Any thoughts on const being used like so:

x := 7  # const x = 7

And all other = being treated as they are today, either using var or let?

@rattrayalex
Copy link
Contributor

Personally, I believe these should be untouched. I can see that there is a case for implementing const in almost any language remit. But not coffeescript.

@DomVinyard re-reading this, I'm not sure I understand what you originally meant. Can you clarify?

@carlsmith
Copy link

Adding a constant operator is easier than adding declarators. It changes the language less. The operator is ok, but will get a bit messy when its x :=? 7. If it was a short word, but still a normal operator, it wouldn't look as cryptic: x be 7 and x be? 7 looks more like regular CoffeeScript. I don't think it should be called be though. Just, if there was a good, short word, that would be better than having a :=? operator :)

@JimPanic
Copy link
Contributor

@rattrayalex It helps avoiding so many bugs and enables a more declarative way of coding, which in turn helps keep code readable and (more) testable. How do you enforce const by default? Does your linter settings just yell at you when you do not declare something as const? :D

@JimPanic
Copy link
Contributor

@carlsmith I was thinking the same; a different assignment operator would go well with the CS syntax as it is now, since there are no declarator keywords. Why the question mark in :=? ?

@carlsmith
Copy link

carlsmith commented Jul 22, 2016

@JimPanic - My bad: I got it back-to-front (I rarely use the feature this way), but you have an existential assignment operator in CoffeeScript that looks like ?=, so if we have a constant assignment operator like :=, we'd need an existential constant assignment operator, which would end up looking like ?:=.

@carlsmith
Copy link

carlsmith commented Jul 22, 2016

We could figure out from static analysis whether a name is assigned more than once, and make names that are never reassigned constants automatically, but expect JavaScript engines already do this, so any efficiency gains would likely be negligible. More importantly, people want explicit constant assignment/declarations to communicate that constraint to other programmers. Given that it's a popular CoffeeScript feature request, and already part of ES6, explicit constants should really be supported somehow.

There's an argument for getting rid of the is and isnt operators from CoffeeScript. CoffeeScript used to have an aint operator that compiled to !==, but it got dropped in favour of just having the current isnt alias, but that's confusing. People often shorten a is not b to a isnt b, expecting them to mean the same thing, like they do in English, but a is not b compiles to a == !b, while a isnt b compiles to a !== b.

If we just kept the current == and != CoffeeScript operators (which compile to === and !==), and got rid of isnt altogether, then is would be a good constant assignment operator.

    i = 0
    pi is 3.141

Then a ?is b would work as an existential assignment operator.

If we do a backwards incompatible CoffeeScript, we should definitely fix anything that is accepted as broken and easy to fix. Removing is and isnt would be trivial. We already have conventional, unproblematic == and != operators. And is would be a really nice constant assignment operator.

It's also worth mentioning that Python has an is operator. It's used to test whether two expressions evaluate to the exact same object in memory.

    a = b = []
    a is b         # True

    a, b = [], []
    a is b         # False

Using is for assigning constants would be a bit different, but still similar to Python - they both kind of mean is actually rather than equals.

@DomVinyard
Copy link
Author

DomVinyard commented Jul 22, 2016

@rattrayalex

Coffeescript succeeded, at least partially, because it traded out efficiency optimisations for simplicity in a bunch of contentious but elegant ways. Not distinguishing between let and const fits that core simplicity brief.

Perhaps this means that a few edge-projects will have to use vanillaJS for certain classes of application because the overhead of trading out lets for consts would trip the project from acceptable into unacceptable but I think that is a reasonable cost to keep things simple.

Avoiding having to add a new operator is worth fighting hard for in every case.. if CS is to retain its CSiness.

@DomVinyard
Copy link
Author

@carlsmith Firm -1 from me for redefining is.

@rattrayalex
Copy link
Contributor

How do you enforce const by default? Does your linter settings just yell at you when you do not declare something as const?

Yes – it's an ESLint setting to always use const unless you redefine the variable later.

we'd need an existential constant assignment operator, which would end up looking like ?:=.

I don't think an existential constant assignment operator makes sense 😉. ?= is used for reassignment, which isn't possible with const.

@rattrayalex
Copy link
Contributor

rattrayalex commented Jul 22, 2016

explicit constants should really be supported somehow.

I'm increasingly convinced of this as well personally. Should probably add to #8 .

Avoiding having to add a new operator is worth fighting hard for in every case.. if CS is to retain its CSiness.

I disagree with this sentiment, actually. While maintaining a decent degree of backwards-compatibility would be nice, I don't think we should shy away from adding new features, especially those that are part of ES6. The number one purpose of this org is to ensure that CoffeeScript isn't behind ES6 at all – that anything* you can do conveniently in JavaScript, you can do at least as conveniently in CoffeeScript.

Furthermore, language innovation is a core aspect of the "CSiness" of CoffeeScript IMO. Jeremy combined the most convenient language features he could find from other languages, made it easy to write good javascript, and added some new ideas into the mix as well.

We should certainly think really hard about whether any new features are worth doing – and focus on compatibility with ES6 first – but I also don't think it's something we should shy away from.

* Anything that you should be doing, anyway. Making "the bad parts" and generally poor programming practices less convenient, or even impossible, is a good thing.

@rattrayalex
Copy link
Contributor

Firm -1 from me for redefining is.

I personally agree. However, to keep a healthy environment where people feel comfortable voicing new ideas they might not be 100% comfortable with yet, let's try to back up -1's with a few calm reasons.

In this case, my reason is primarily difficult of porting – redefining a language keyword to something completely different could make updating quite difficult, as you'd have to both translate all existing =s to is, and only once that is 100%-for-sure-done across your whole project, change most of your =s to is.

I also happen to think it'd be a bit less readable than something with symbols, given the ubiquity of the = for assignment across programming languages.

@rattrayalex
Copy link
Contributor

My understanding is the primary objection to := is the conditional assignment scenario, which as I outlined above probably doesn't apply. Are there others?

I think the leading alternative at this point would probably be to just pass const through, or come up with a similar (ideally shorter) word.

Thoughts?

@carlsmith
Copy link

carlsmith commented Jul 22, 2016

I don't think an existential constant assignment operator makes sense 😉. ?= is used for reassignment, which isn't possible with const.

I didn't think about that. Sorry about the noise then. I mentioned not using that feature much when I misspelled the operator :|

I'm with @rattrayalex on adding new features, but feel pretty strongly that we should stick to things that are just sugar for well established idioms or that have already been standardised by ECMA. CoffeeScript should make one big breaking change, then treat TC39 as our standards process. Now that CoffeeScript has inspired JavaScript to evolve, we can co-evolve with JavaScript in a way Jeremy couldn't in 2009.

So, yeah - sorry to ramble - assuming there will be no need to have an existential constant assignment operator, then I would personally be ok with :=, but still wish it was prettier.

square := (x) -> x * x

I still feel strongly that we should avoid declarators if at all possible. It changes the language, and we use assignment expressions in lots of places where they need to be concise, not just in standalone statements..

Also, do we need a story for constant function parameters, or can you just not define a parameter as a constant?

@rattrayalex
Copy link
Contributor

but still wish it was prettier.

Agreed.

remember that we use assignment expressions in lots of places where they need to be concise.

Would you be willing to provide a few quick examples for illustrative purposes?

or can you just not define a parameter as a constant?

Well, you can't in es6, so we'd really be going above and beyond if we implemented it in cs6. It's not an unappealing feature imo but probably not something to worry about yet.

CoffeeScript should make one big breaking change

Amen.

then treat TC39 as our standards process

I really need to learn more about TC39 😃

@carlsmith
Copy link

carlsmith commented Jul 23, 2016

Would you be willing to provide a few quick examples for illustrative purposes?

I meant using assignment expressions like this:

if (stuff = getStuffOrReturnNull) then use stuff

employees = [
    ali = new Employee "Ali"
    bob = new Employee "Bob"
]

Because assignments are expressions, you can write one anywhere you can write an expression. It can be helpful when you want to make assignments conditionally, and lets us use some shorthand expressions for initialising variables.

@carlsmith
Copy link

On the TC39 comment: I really just meant that we should follow the ECMAScript standards when introducing features that go beyond sugar for common JS idioms.

We have the luxury these days of a standards process that moves quickly, releases often and develop editions in parallel. It has some of the best minds in our community, and all the vendors are on board. You can get involved through ESDiscuss, and influence the spec, but for the most part, we don't need to. It works.

If Jeremy had followed the spec, we wouldn't have the problem we now have with incompatible classes. Obviously, in 2009, following the spec would have made CoffeeScript suck.

@rattrayalex
Copy link
Contributor

Wow, I've never seen the employees code before. Looks awesome though.

https://github.com/michaelficarra/CoffeeScriptRedux/wiki/Intentional-Deviations-From-jashkenas-coffee-script#intentional-deviations might be relevant here once we get into the weeds, especially if we decide to build on CSR.

Thanks for sharing the examples! Helpful.

@carlmathisen
Copy link

You're right @rattrayalex, sorry about the tangent.

GeoffreyBooth added a commit that referenced this issue Aug 23, 2016
* Merge GeoffreyBooth proposal into README

* Split out some of the overlong README into separate files

* Restore alternatives section

* Better filenames

* Force detect case change #1

* Force detect case change #2
@GeoffreyBooth
Copy link
Collaborator

GeoffreyBooth commented Sep 6, 2016

Carrying over a discussion from #30, is interoperability affected if we don’t add support for const? Like will there be any library we can’t use if we can’t send a const-defined variable into it, for example? Or perhaps a build tool that optimizes based on const vs var or let?

One sort-of case I can think of is mentioned in this comment on the modules PR. Basically, anything you import is essentially a const. In this:

import foo from 'lib'

foo is read-only, meaning it’s essentially a const; if you try foo = 2 later on, an error will be thrown. I wonder if it’s weird to have these implicit constants defined via import, while no other constants can be created in any other way. @greghuc, with regards to your ”mental model” of needing to keep track of which variables are read-only and which aren’t, unfortunately with module support you have no choice but to fight that battle (assuming you want to use modules).

@zeekay
Copy link

zeekay commented Sep 7, 2016

Any thoughts on const being used like so:
x := 7 # const x = 7

I'm not a fan as := for const declaration as it's used in Go (and other languages) for variable declaration. If we were going to break nonlocal assignment in CoffeeScript I'd prefer to use := for new variables and = for assigning to an existing variable (as in Go). This makes assigning to a variable in an outer scope very clear and tends to actually prevent bugs whereas I've never found const to provide much value.

As const does seem to be fairly popular though, support makes sense. I'd argue that we should just we make const pass through like other ES6 constructs.

const x = 7

This is perfectly acceptable and matches JavaScript which is preferable to new syntax.
It's readable and fine, in my opinion.

Coming from Python, CoffeeScript's nonlocal assignment was kind of confusing, at least initially. I no longer find it very difficult to reason about, but I cannot think of a single person I've met who is a fan of it (and of course it's been written about for quite some time, never positively). Personally, I would not mind ditching it in favor of implicit let or explicit :=/=.

@GeoffreyBooth
Copy link
Collaborator

@zeekay I think := was selected because const can only be assigned as part of its declaration, so it was appropriate. There is a strong convention in CoffeeScript to not use keywords for declaration (hence no var, no function, etc.; exceptions exist like class, but I don’t think people want to add any more). We can’t break backwards compatibility, so changing the behavior of = is not an option.

By nonlocal assignment, I assume you mean CoffeeScript’s habit of creating a var line at the top of whatever scope you’re in and declaring all variables there, then assigning them wherever you reference the variable? That won’t be possible with const: by definition, it needs to be assigned when it’s declared. I assume that support for const would put its declaration/assignment at the same place in the code that you typed it in CoffeeScript, rather than moving it up to the top of the scope.

@zeekay
Copy link

zeekay commented Sep 7, 2016

By nonlocal assignment I was referring to how it's not possible to shadow an outer variable:

x = 1
do ->
    x = 2 # x assigns to nonlocal x above
x # == 2

Which is the terminology used above in this discussion (as a nod to Python's nonlocal statement, I believe). The default of nonlocal assignment is very confusing to newcomers and does lead to bugs, in my experience.

I would argue that the lack of keywords for declaration was due to lack of necessity (when CoffeeScript was introduced there was only var to be concerned with). CoffeeScript has actually introduced a great number of keywords (loop, do, is, isnt, etc). I tend to find keywords easier to read and reason about compared to new syntactic structures and that's been one of the things I've come to appreciate about CoffeeScript.

@zeekay
Copy link

zeekay commented Sep 7, 2016

As a quick follow-on: I'd be fine with changing declaration behavior. Compiling normal declarations directly to let would lead to an improvement in the readability of the compiled JavaScript and more closely match what modern JavaScript developers would write by hand.

The more interesting bit of the discussion in my mind is what to do (if anything) about CoffeeScript's (for lack of better terminology) nonlocal assignment. It might be nice to introduce := as a way to force declaration, which would not break backwards compatibility:

x = 1
do ->
    x := 2 # declares a new x
x # == 1

Which would be a better use of := than as shorthand for const.

@GeoffreyBooth
Copy link
Collaborator

@zeekay may I invite you to open an issue suggesting changing nonlocal assignment? And the community can debate its merits there.

This thread covers both explicit const assignment (the proposed := operator) and automatic usage of let when appropriate. These are really two separate feature requests, especially considering that the former can be implemented today without a flag (since it’s opt-in by using the new syntax) while the latter can’t (since it’s automatic). I created #31 to cover the proposed new operator. Automatic lets are much farther in our future, when we’re onto the task of converting all of CoffeeScript’s output to be as ES-latest as possible, so I’m not going to bother creating an issue for it (yet). But feel free if you want to discuss it further.

@mrmowgli
Copy link

mrmowgli commented Sep 7, 2016

I can see why the const becomes important, especially when dealing with code security. Let being the counterpart to avoid intentional abuse of the browser environment. I don't think anything would directly break, but I do think there is a good reason to include the functionality. Perhaps there are some intelligent rules to creating let assignments automatically. +1 for := assuming let and keeping the const keyword.

@rattrayalex
Copy link
Contributor

@GeoffreyBooth not sure if anyone answered your question:

is interoperability affected if we don’t add support for const?

I don't believe so, no. const is a feature people like, but not an interoperability concern.

@carlsmith
Copy link

I can't see any way it could undermine interoperability either. It's the reference that is constant, not the value, and a library wouldn't even see the name, unless you were sharing globals. Every interface is focussed on the values, never the way they're expressed.

@rattrayalex
Copy link
Contributor

My understanding of our consensus:

  1. We'll probably just keep compiling to var for the time being, as their is no practical benefit of using let or const in the output.
  2. const may be implemented with := or something similar, but won't be inferred; discussion for that has moved to CS2 Discussion: Features: const assignment operator #31 .
  3. At some point in the future, coffeescript may compile to let/const instead of var, but it's not a priority and not worth discussing now.
  4. The decaffeinate project can be used if you want ES6 output today.

As such, I am closing this issue for now. If I get sufficient 👎 reactions to this comment, we'll re-open.

@GeoffreyBooth
Copy link
Collaborator

GeoffreyBooth commented Sep 9, 2016

If I could amend that consensus a little, @zeekay described an alternate proposal on Gitter that he’s planning to present as a new issue soon, probably this weekend. Basically his point is that the great advantage of let/const is its block-scoping, i.e.:

let a = 1;
if (true) {
  let b = 2;
}
console.log(b); // undefined

This is a dramatic improvement over var, and a big reason why let and const have become popular features. We need to at least discuss if this is something CoffeeScript should have, separate from the feature of const that means “throw an error on reassignment.”

I think we could have both, via := and perhaps :== operators (or whatever two operators people think are best):

  • a := 1 would mean, “declare and assign a with the value of 1, using let if a gets reassigned in this block and const if it never gets reassigned.”
  • a :== 1 would mean, “declare and assign a with the value of 1 using const. If it gets reassigned later, throw an error.”

We don’t necessarily need the second operator, if we don’t care to give people a way to force const. I’m not sure how popular the “throw an error on reassignment” feature is, but I suspect it’s quite popular.

Agreed that we should close this issue though, in favor of #31 and whatever new issue is created to discuss this “lexical assignment operator”.

jez added a commit to jez/coffeescript that referenced this issue Sep 19, 2017
I found [this discussion][1] talking about `let` and `const` to be
helpful to understand the decisions why these keywords were not added to
CoffeeScript v2.

In particular, I think we can improve the documentation by linking to
these examples on the section about why they're unsupported. Basically,
it comes down to using `do ->` to approximate `let` and `const`.

I'm down to re-word anything here, and I also completely understand if
this is not something you want in the documentation.

[1]: coffeescript6/discuss#1
@GeoffreyBooth GeoffreyBooth changed the title let/const/var CS2 Discussion: Features: let/const/var Feb 19, 2018
@coffeescriptbot
Copy link
Collaborator

Migrated to jashkenas/coffeescript#4900

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests