manuel / wat-js

Concurrency and Metaprogramming for JS
MIT License
259 stars 10 forks source link

Scoping by copy is becoming somewhat limiting #6

Open shadowcat-mst opened 11 years ago

shadowcat-mst commented 11 years ago

While it's not really a good example of why you'd want to, I was trying to get -

let [[i 0]] [begin [while [< i 5] [def i [+ i 1]]] i]

to work and return 5 - except of course def creates a symbol in the current environment ... and in any case, unless I'm hallucinating the i visible to the body of the while is a copy of i in the lambda's environment rather than the original i due to the behaviour of make-environment.

I did, actually, remember to read wat-basics this time (hence finding while at all ;) but I'm not sure how to fix this - turning the environments into a chain and offering some sort of lvalue access seems like the default approach but it'd introduce noticeable additional complication.

Any other ideas? If environment shadowing is the answer, shall we try and thrash out the desired semantics as a first case for shared tests?

manuel commented 11 years ago

You can define set! like this: https://github.com/manuel/wat-js/blob/0045525ae01e5431178af56d243524ccb7a701a4/crust.wat#L142

It's one of the sore spots of Kernel IMO. Using this definition of set!, you'd have to capture the environment i appears in using current-environment https://github.com/manuel/wat-js/blob/0045525ae01e5431178af56d243524ccb7a701a4/crust.wat#L155 and then update i within that enviroment using set!

Every time ["def", "i", ...] is called, it creates a new binding in the current (the loop's) environment. There's no copying. When the loop begins from the start again, the environment is thrown away. Hm... this is probably a bug in the definition of WHILE caused by its use of LAMBDA: it shouldn't create a fresh environment for its body expressions, but rather evaluate them in the environment WHILE appears in. It's unrelated to the issue you're describing though. http://this-plt-life.tumblr.com/post/44079123074/when-im-working-on-a-new-pl-most-of-the-time

shadowcat-mst commented 11 years ago

Right, it's make_env that does the copying. I'm basically suggesting that make_env instead creates a chain, so it keeps a reference to the 'outer' environment and looks things up by walking the outer chain.

That way set! can operate on the environment that the value was defined in. Basically I'm looking for lexical closures, ala

my $x = 5;
my $inc_x = sub { $x++ };
$inc_x->();
warn $x; # 6

Seems like it could be done by modifying make_env and lookup and adding an additional built-in __SetBang or something, then wrapping that into set with something like

vau [sym val] #ignore [if symbol? sym] [__set sym val] [throw "Not a symbol"]]

and code in lookup along the lines of

my $lenv = $env;
while ($lenv) {
  return $lenv->{$sym_name} if exists $lenv->{$syn_name};
  $lenv = $lenv->{outer};
}

Unless you have any better ideas I'll probably trial this in wat-pl shortly.

manuel commented 11 years ago

make_env in the JS version does exactly what you're describing - creating a new environment with a link to a parent environment, in which bindings are looked up if they aren't found in the current environment.

shadowcat-mst commented 11 years ago

Ah fsck I read it based on Object.create being a copy constructor ... of course it takes a prototype and just chains a new object off it.

I'll adjust my make_env implementation accordingly; I'll have to do it more manually in Perl but that'll probably make set! easier to implement as a result.

manuel commented 11 years ago

Well yeah, you can add a Scheme-like set! as a primitive, similar to def. It looks for an existing binding in the chain of environments, and modifies it if it finds one. If it doesn't find one, the clean thing is to throw an error.

Great work with your Perl version. I'm finally getting around to using Wat in a real app. I'll write some tests for the core functionality as a plain JSON file which you should be able to eval in your Perl version.

shadowcat-mst commented 11 years ago

I've just added such a set! plus a test for the let+while+set! combo described above; turned out to be as trivial as I hoped once I fixed the scoping model. I'll report back if I suddenly find myself missing a foot.