wisp-lang / wisp

A little Clojure-like LISP in JavaScript

Home Page:https://gozala.github.io/wisp/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`do` does not retain scope

kballenegger opened this issue · comments

The do form does not retain its parent scope, the way e.g. let does.

(do () ()) generates (function () { void 0; return (void 0); })(), instead of (function () { void 0; return (void 0); }).call(this)

I think you're right, wanna try to tackle this issue ? I can give some tips how to approach it.

I can try to give it a try when I get a chance. Gonna have to find some time though, might take a few weeks (months?).

commented

So for me this (transpile) returns

(function () {
    list();
    return list();
})();

Also trying (do (def foo 1) (do (print foo) ())) seems to me it actually does retain scope. Or are we talking macro's here? Also there, in my book, the output is sane. Trying

(defmacro foo [a]
  (do
    (def b 1)
    (do
      (print a)
      (print b)
      (print *ns*)
  ())))
;=> void(0);
(print (foo :bar)) ;=> compiles to console.log(list());
(foo :bar) ;=> compiles to list();

Both print :foo and 1 on executing. Note, the keyword printing out literal, e.g. expansion (*ns* will resolve to wisp.backend.escodegen.generator) is taking place and here a keyword is just a wisp keyword, not a string. Added the *ns* print to illustrate.

Imho, this makes perfect sense. Although there is no way to view the expression (the defmacro 'vaporizes' as a figure of speech) I can't show the expression (isn't there a macro-expand macro usually to serve this end?) but as a function I would assume it is just the same output only outside runtime context, during compile-time.

Taking my macro as function to transpile, I get:

var foo = exports.foo = function foo(a) {
        return (function () {
            var b = 1;
            return (function () {
                var b = 1;
                console.log(a);
                console.log(b);
                console.log(_ns_);
                return list();
            })();
        })();
    };

Note the duplication of the var b = 1 while I only defined it once. This way the value is carried over without touching the calling context of the function expression. Heh, I didn't expect that but yea, it makes sense. So this makes me think Gozala actually fixed this issue since then, and this can be closed??

If you used an arrow function from ES6, the problem would be solved.

(do
  (def foobar "foo")
  (console.log foobar))

generates

(() => {
  var foobar = "foo";
  return console.log(foobar);
})();

Of course this would end up meaning that the programs generated are then only compatible with environments that use ES6, so a fall-back mechanism should be supported if you wish to generate ES5 compliant code and get similar behavior.

Arrow functions are more efficient than binding/calling a normal function for multiple reasons if they are available.

  1. there is no binding/rebinding going on,
    they execute in the same context.
  2. Arrow functions do not have an arguments object, since
    the do macro does not need to have any
    arguments passed to it, it is not needed,
    and not having it would result in faster execution.

@robjens They aren't talking about lexical scopeing of variables here, they are talking about the context of this. Using a self executing function expression with out binding/calling/applying it results in the content of that function useing the head object as its this.