Open Inve1951 opened 1 year ago
This sounds cool! (if somewhat hard to implement) A nice feature is that let
is already a keyword in CoffeeScript, so this won't break existing code.
One note is that let a = a
doesn't give you access to the shadowed a
variable in JavaScript, so your example output needs adjustment. One approach is let a1 = a
and relabel all instances of a
within the lexical scope to a1
. Another approach is do
-style IIFEs. Maybe there's something better?
I also find it a little weird that let a
initializes with a value — it's very counter to the equivalent JavaScript code. I could imagine wanting let a
just declare that a
is a local variable that shadows something else, without initializing it (though this could be accomplished in your proposal with let a = undefined
). I agree that it'd be convenient for replacing do
blocks to just write let a
.
I've also long been a proponent of for let
support in CoffeeScript, as in for let item in array
or for let item from iterable
, which would be a natural extension of this proposal (I think). In my experience, this would remove most need for do
.
For what it's worth, Civet supports let a
as a way to override automatic variable declaration, but doesn't yet support let a = a
for bringing the parent scope's value for a
into the child scope. I think this would be a nice extension, though.
For code generation of the let a = a
where a is in the outer scope it could go something like:
a = 1
b = (x) ->
let a
let y = 2
---
var a, b;
a = 1;
b = function (x) {
var y;
const a1 = a;
{
let a = a1;
y = 2;
}
};
That way only a transient ref is needed rather than relabeling all identifiers in a scope.
I prefer implicit const with no parentheses and no commas:
fn = x1 x2 -> x1 * lg x2
---
const fn = (x1, x2) => x1 * Math.log(x2)
That’s ambiguous. You’re breaking pretty much every single callback in coffee.
I was so sure let a = a
is valid JS. :eyes: TIL
I don't know if it's better to rename user variables or output the bloat:
const a1 = a;
{
let a = a1;
// ...
}
Both options kinda suck. Could still collapse multiple let
s into a single such block which might be an ok compromise.
Edit: I think it's fine. The user gets correct variable names and downstream tooling can strip the block. (minification, dead code elimination, etc)
Not being able to shadow variables in an upper scope or a calling function is a PITA.
In MoonScript, which is to Lua what CS is to JS, all variables not initialized or declared in an upper scope are local by default unless explicitly made global with export foo
,[^export] and you can use the local foo
statement to explicitly shadow variables from an upper scope, although unlike in Lua you cannot assign in the local
statement which I consider a misfeature in MS.
Unlike JS/CS variables declared in calling functions are thankfully not visible in called functions in Lua/MS (unless the callee is a closure within the caller) but MS has the using
keyword which would be even more useful in CS: if you in MS say some_func = (foo using bar, baz) ->
(or just some_func = (using bar, baz) ->
) all assignments within some_func
other than to bar
and baz
are implicitly local even if a variable of the same name exists in an upper scope. You can even say other_func = (foo using nil) ->
(or other_func = (using nil) ->
), where Lua/MS nil
corresponds to JS/CS null
, and all assignments inside the function become implicitly local. I wish you could say do using ...
too in MS, but alas you can't (yet).
[^export]: In Lua/MS a module is simply a script which return
s some value, usually a table (corresponding to both object and array) with functions at the bottom. The statement foo = require"rel.path.to.foo"
simply assigns the return value of rel.path.to.foo.lua
or rel.path.to.foo.moon
or rel.path.to.foo.so
to the local foo
variable, looking up the required script in the paths listed in the LUA_PATH
and LUA_CPATH
environment variables.
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: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
await
keyword 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
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 fromfunc
early by not introducing a new function boundary.Also, if above code used slim arrows
->
instead of fat ones (=>
), the potentialthis
association is lost. Another thing to watch out for.Context
There have been many requests to support JS'
let
andconst
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:var
declaration to the enclosing function body if applicable, generating an in-placelet
instead.undefined
. (a = let b
is illegal)let a = 1, b, c = 4
)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 explicitdo =>
andlet
can still be used when needed, exposing the user to the usual pitfalls associated with it (e.g. having to conditionallyawait 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 JSlet
statement. 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: