Variable scopes mismatching between Roy and JavaScript
Opened this issue · 6 comments
Problem
The variable scopes in JavaScript are whole function; however scopes in Roy are only after the let declaration.
This results in referencing uninitialized variables.
Sample Code
let x = 1
let f = \() ->
console.log(x)
let x = true
x
console.log(f())
Expected
1
true
Actual
undefined
true
Cause
The code is compiled into
var x = 1;
var f = function() {
console.log((x));
var x = true;
return x;
};
console.log(f());
The scope in JavaScript are whole function, thus the code is equivalent to
var x = 1;
var f = function() {
var x;
console.log((x));
x = true;
return x;
};
console.log(f());
Possible Fix
I guess there are two ways (or more?) but neither is satisfactory.
Option1: Adapting to Roy semantics
Compile the code into
var x = 1;
var f = function() {
console.log((x));
return (function() {
var x = true;
return x;
})();
};
console.log(f());
or
var x = 1;
var f = function() {
console.log((x));
var x1 = true; // renaming the variable
return x1;
};
console.log(f());
Both mess compiled code to some extent.
Option 2: Adapting to JavaScript semantics
Raise error if referencing uninitialized variable.
This is inconvenient and not intuitive.
Match expression has same problem.
The code below outputs undefined.
data Option a =
Some a | None
let x = 2
match (None ())
case None = console.log x
case (Some x) = console.log x
Compiled code:
...
var x = 2;
(function() {
if(None() instanceof None) {
return console.log(x);
} else if(None() instanceof Some) {
var x = None()._0;
return console.log(x);
}
})();
which is equivalent to
...
var x = 2;
(function() {
var x;
if(None() instanceof None) {
return console.log(x);
} else if(None() instanceof Some) {
x = None()._0;
return console.log(x);
}
})();
My original idea with let binding was to generate option 1 when a conflict was detected.
But I want the compiler to be smart enough to not have to place function expressions everywhere; it should only put them in places that a native JavaScript developer would want to place them.
I think it is acceptable. There seems to be no ideal solutions.
Yes, Option 1 is very acceptable to me. My preferred compilation:
var x = 1;
var f = function() {
console.log(x);
return function(x) {
return x;
}(true);
};
console.log(f());
For expressions without side effects (or if the new var is referenced at most once), the instances in the continuation can be rewritten to the assigned value:
let x = 1
let f = \() ->
console.log(x)
true
console.log(f())
var x = 1;
var f = function() {
console.log(x);
return true;
};
console.log(f());
... which is the "ideal" solution, though unfortunately not possible in all cases. IIFEs would be needed for things like this:
let x = 1
let f = \() ->
console.log(x)
let x = g()
console.log(x)
x
console.log(f())
var x = 1;
var f = function() {
var __temp;
console.log(x);
return function(x){
console.log(x);
return x;
}(g());
};
console.log(f());
The drawback of IIFEs for things like this is that they interfere with TCO - see the TCO PR discussion.
Given that, I think renaming the shadowed var (e.g. var x2 = ...
) is the best remaining option.