Do you have thoughts on a let keyword for coffeescript?
Let me walk you through my idea below.
It could be syntactic sugar for the current do (a, b, x) => expression, while avoiding extra indentation, like this:
a = null
func = ->
let a = 1 # capture `a` in local scope and set it to 1.
for b in c # saved a level of indentation
let b # promote b into local scope
2 # using `await` would be fine too
In current coffeescript this can almost be expressed like so:
a = null
func = ->
do (a = 1) => # extra level of indentation
for b in c then do (b) => # breaks flow if async
2
But as annotated, this loop becomes a problem when the loop body (the IIFE's body) uses the await keyword because that changes the return type of func, from a Promise that will resolve to an Array, to an (already resolved) Array of Promises. The loop's iterations now run in parallel. If you did not intend for this you now have a critical bug, at least waiting to happen.
So really it would have to be this code to preserve meaning:
a = null
func = ->
do (a = 1) =>
do => for b in c # note that `do (b) =>` is illegal without shadowing
2
In above example the let keyword eliminates the syntactic cruft necessary to express the same scoping rules in a bug-free manner, making it easy to write correct code. It also enables the loop to be a generator and to return from func early by not introducing a new function boundary.
Also, if above code used slim arrows -> instead of fat ones (=>), the potential this association is lost. Another thing to watch out for.
Context
There have been many requests to support JS' let and const with the primary driver being finer control over variable scopes.
The let "statement" proposed here addresses general scoping concerns in a readable manner. I'd define its rules like this:
- It promotes identifiers already found in lexical scope to local scope.
- It suppresses bubbling a
var declaration to the enclosing function body if applicable, generating an in-place let instead.
- An initialization expression can optionally assign to the captured (locally-scoped) identifier.
- It has no expression value / is disallowed as an expression. If a branch evaluates to it, it evaluates to
undefined. (a = let b is illegal)
- It can be used only 1 identifier at a time. (no
let a = 1, b, c = 4)
- It could be allowed to use
let more than once with the same identifier in the same block scope by generating a { let ... } block, but this is maybe unintentional. Without this, an explicit do => and let can still be used when needed, exposing the user to the usual pitfalls associated with it (e.g. having to conditionally await do =>). let would solve this automagically but arguably it's a corner case and the compile error may be preferred.
Simply put, the proposed let statement outputs a JS let statement. But additionally it initializes to the shadowed identifier's value, if any:
Input:
a = 1
b = (x) ->
let a
let y = 2
Output:
var a, b;
a = 1;
b = function(x) {
var y;
let a = a;
y = 2;
};
Note: With #5377 implemented all var statements in the output will be omitted in favor of let/const at the assignment site, and let a = a becomes const a = a.
Possible future extensions include other assignment operators, which could operate on the shadowed identifier's value to initialize the local capture.
Conclusion
This proposal gives people their block scoping and offers a readable, unambiguous solution to common variable capturing issues, avoiding the pitfalls and mental overhead associated with current workarounds.
It also supersedes do (a, b = 1) => expressions. They can now be expressed as:
- current CoffeeScript version: 2.7.0
Do you have thoughts on a
letkeyword for coffeescript?Let me walk you through my idea below.
It could be syntactic sugar for the current
do (a, b, x) =>expression, while avoiding extra indentation, like this:In current coffeescript this can almost be expressed like so:
But as annotated, this loop becomes a problem when the loop body (the IIFE's body) uses the
awaitkeyword because that changes the return type offunc, from a Promise that will resolve to an Array, to an (already resolved) Array of Promises. The loop's iterations now run in parallel. If you did not intend for this you now have a critical bug, at least waiting to happen.So really it would have to be this code to preserve meaning:
In above example the
letkeyword eliminates the syntactic cruft necessary to express the same scoping rules in a bug-free manner, making it easy to write correct code. It also enables the loop to be a generator and to return fromfuncearly by not introducing a new function boundary.Also, if above code used slim arrows
->instead of fat ones (=>), the potentialthisassociation is lost. Another thing to watch out for.Context
There have been many requests to support JS'
letandconstwith the primary driver being finer control over variable scopes.The
let"statement" proposed here addresses general scoping concerns in a readable manner. I'd define its rules like this:vardeclaration to the enclosing function body if applicable, generating an in-placeletinstead.undefined. (a = let bis illegal)let a = 1, b, c = 4)letmore than once with the same identifier in the same block scope by generating a{ let ... }block, but this is maybe unintentional. Without this, an explicitdo =>andletcan still be used when needed, exposing the user to the usual pitfalls associated with it (e.g. having to conditionallyawait do =>).letwould solve this automagically but arguably it's a corner case and the compile error may be preferred.Simply put, the proposed
letstatement outputs a JSletstatement. But additionally it initializes to the shadowed identifier's value, if any:Input:
Output:
Possible future extensions include other assignment operators, which could operate on the shadowed identifier's value to initialize the local capture.
Conclusion
This proposal gives people their block scoping and offers a readable, unambiguous solution to common variable capturing issues, avoiding the pitfalls and mental overhead associated with current workarounds.
It also supersedes
do (a, b = 1) =>expressions. They can now be expressed as: