Arc Forumnew | comments | leaders | submitlogin
2 points by fallintothis 5097 days ago | link | parent

[Insert caveats about how crazy the idea is, yadda yadda, stuff rntz already pointed out, blah blah, who cares?]

There are too many edge-cases for the idea to work well once implemented (as I said in the giant rant above :P), but you can get the basic behavior down, which fixes the issues you were finding early on in the thread. I gather that's what you were asking. So, for the sake of fun:

  (mac lexbound (x)
    `(if ((locals) ,x)
         t
         (bound ,x)))

  ; ac won't care about shadowing a special-form, but we don't want to treat it
  ; like an arg to the br-fn below

  (def keyword (name)
    (or (nonop name)
        (in name 'nil 't 'if 'fn 'assign 'locals)))

  (mac make-br-fn (body)
    (w/uniq args
      `(fn ,args
         (with ,(mappend
                  (fn (v) `(,v (if (lexbound ',v) ,v
                                   (no ,args)     (err "Too few args to br-fn")
                                                  (pop ,args))))
                  (sort < (keep (fn (_) (and (isa _ 'sym) (~keyword _)))
                                (dedup (flat body)))))
           (when ,args
             (err "Too many args to br-fn"))
           ,body))))

  arc> ((make-br-fn (+ x y z)) 1 2 3)
  6
  arc> ((make-br-fn (+ x y z)) 1 2 3 4)
  Error: "Too many args to br-fn"
  arc> ((make-br-fn (+ x y z)) 1 2)
  Error: "Too few args to br-fn"
  arc> (let x 3 ((make-br-fn (+ x y z)) 1 2))
  6
  arc> (let + (fn (x y z) (prf "x = #x, y = #y, z = #z") (prn))
         ((make-br-fn (+ x y z)) 1 2 3))
  x = 1, y = 2, z = 3
  nil
  arc> ((make-br-fn (do (prs x z y) (prn))) 1 2 3) ; bound in alphabetic order
  1 3 2
  nil
You have to do the work within the expansion itself (at run-time), since that's where you need to check whether variables are lexically bound. I.e., if you do it in the macro, you'll just get the locals from macroexpansion time. This essentially works the same way the following do.

  arc> (let x (if (lexbound 'x) x 5)
         (+ x 5))
  10
since the call to locals winds up happening outside the outside of the fn that let generates:

  arc> (macsteps '(let x (if (lexbound 'x) x 5) (+ x 5)))
  Expression:

    (let x (if (lexbound 'x) x 5)
      (+ x 5))

  Macro Expansion:

      (let x (if (lexbound 'x) x 5)
        (+ x 5))

  ==> (with (x (if (lexbound 'x) x 5))
        (+ x 5))

  Expression:

    (with (x (if (lexbound 'x) x 5))
      (+ x 5))

  Macro Expansion:

      (with (x (if (lexbound 'x) x 5))
        (+ x 5))

  ==> ((fn (x) (+ x 5)) (if (lexbound 'x) x 5))

  Expression:

    ((fn (x) (+ x 5)) (if (lexbound 'x) x 5))

  Macro Expansion:

      (lexbound 'x)

  ==> (if ((locals) 'x) t (bound 'x))

  Expression:

    ((fn (x) (+ x 5)) (if (if ((locals) 'x) t (bound 'x)) x 5))

  nil
Whereas

  arc> (let x 10
         (let x (if (lexbound 'x) x 5)
           (+ x 5)))
  15
since the call to locals winds up happening inside the fn that the outermost let generates:

  arc> (macsteps '(let x 10 (let x (if (lexbound 'x) x 5) (+ x 5))))
  Expression:

    (let x 10
      (let x (if (lexbound 'x) x 5)
        (+ x 5)))

  Macro Expansion:

      (let x 10
        (let x (if (lexbound 'x) x 5)
          (+ x 5)))

  ==> (with (x 10)
        (let x (if (lexbound 'x) x 5)
          (+ x 5)))

  Expression:

    (with (x 10)
      (let x (if (lexbound 'x) x 5)
        (+ x 5)))

  Macro Expansion:

      (with (x 10)
        (let x (if (lexbound 'x) x 5)
          (+ x 5)))

  ==> ((fn (x)
         (let x (if (lexbound 'x) x 5)
           (+ x 5)))
       10)

  Expression:

    ((fn (x)
       (let x (if (lexbound 'x) x 5)
         (+ x 5)))
     10)

  Macro Expansion:

      (let x (if (lexbound 'x) x 5)
        (+ x 5))

  ==> (with (x (if (lexbound 'x) x 5))
        (+ x 5))

  Expression:

    ((fn (x)
       (with (x (if (lexbound 'x) x 5))
         (+ x 5)))
     10)

  Macro Expansion:

      (with (x (if (lexbound 'x) x 5))
        (+ x 5))

  ==> ((fn (x) (+ x 5)) (if (lexbound 'x) x 5))

  Expression:

    ((fn (x)
       ((fn (x) (+ x 5)) (if (lexbound 'x) x 5)))
     10)

  Macro Expansion:

      (lexbound 'x)

  ==> (if ((locals) 'x) t (bound 'x))

  Expression:

    ((fn (x)
       ((fn (x) (+ x 5)) (if (if ((locals) 'x) t (bound 'x)) x 5)))
     10)

  nil
That said, there are plenty of bugs.

- The most obvious is that you'd need a full-blown walker of the body argument to see where you really need to make new variables. E.g.,

  (make-br-fn (let x 5
                (+ x 10)))
macroexpands to

  (fn gs2296
    (with (+   (if (lexbound '+) +
                   (no gs2296)   (err "Too few args to br-fn")
                                 (pop gs2296))
           let (if (lexbound 'let) let
                 (no gs2296)       (err "Too few args to br-fn")
                                   (pop gs2296))
           x   (if (lexbound 'x) x
                   (no gs2296)   (err "Too few args to br-fn")
                                 (pop gs2296)))
      (when gs2296
        (err "Too many args to br-fn"))
      (let x 5
        (+ x 10))))
which winds up expecting an x argument, because it wasn't bound outside of the br-fn, even though it's just a local binding introduced in the body by the let.

  arc> ((make-br-fn (let x 5 (+ x 10))) 'ignored-x)
  15
- make-br-fn should probably start with (zap macex-all body), since macros may introduce arbitrary unbound variables. E.g.,

  arc> (macex1 '(prf "x = #x"))        ; introduces an unbound reference to x
  (let gs396 (list) (pr "x = " x))
  arc> ((make-br-fn (prf "x = #x")) 1) ; prf not expanded
  Error: "Too many args to br-fn"
Couple this with the above bug, and you have even more problems, since in the above expansion of prf, you can't tell that gs396 is a bound local in body without doing a code-walk.

- You'll still have problems from vanilla Arc's lexically-shadowed-macros bug in some cases, but they're the ones you'd expect from knowing about the bug to begin with. That is, the following works because do is not in a function position.

  arc> (let do 10 ((make-br-fn do)))
  10
But, instead of generating an error (applying 10 to 5), the following macroexpands do.

  arc> (let do 10 ((make-br-fn (do 5))))
  5
This thread's made local variables in Arc interesting in ways I hadn't thought of before. Macros introduce odd contours to lexical environments that don't exist in, say, Python (which still has a locals() function). Basically, any use of (locals) should come with a big, fat warning: "magic happens here".