Arc Forumnew | comments | leaders | submitlogin
2 points by rocketnia 3862 days ago | link | parent

Where you said this...

  foo <- (fn (n) (eval `(+ ,n 1) caller_scope))
...I think you meant to say this, with an unevaluated argument list and another (fn ...) form:

  foo <- (fn '(n) (eval ((fn () `(+ ,n 1))) caller_scope))
That said, it's okay to drop the ((fn () ...)) in this case, since the code inside doesn't refer to caller_scope.

---

"I'm still not convinced I fully understand why eval and fn each add an indirection to caller_scope ."

(For the moment, I'm going to overlook eval here.)

That's just how you chose to build it, right? Isn't the whole point of caller_scope that it's specific to the current call?

I think the main issue you're encountering is that you always use the same name, "caller_scope", resulting in shadowing. Kernel uses an explicit parameter, as in ($vau () env ...), but Wart doesn't. This isn't necessarily a problem, because you can do something like this to work around the shadowing when necessary:

  (fn (x)
    (let my_scope caller_scope
      (fn (y)
        ...)))
If you actually want to call a variable "caller_scope" even though it's not the current caller scope, you might have trouble with that, but you do have a couple of options.

I think this code will evaluate the ... as though it's just inside the (fn (x) ...), so that it can see the correct binding of caller_scope:

  (fn (x)
    (let fnx_scope ((fn () caller_scope))
      (fn (y)
        (eval '... fnx_scope))))
Unfortunately, the ... can't see the binding of y.

If you tweak the language a bit, you could make the implicit caller_scope parameter get shadowed by the explicit parameters. That is, if a function is of the form (fn (caller_scope) ...), you could let it ignore its implicit caller_scope binding altogether. (Currently Wart treats this just like any other duplicate argument name, and it throws an error.) In that case, you could use this technique:

  (fn (x)
    (let my_scope caller_scope
      (fn (y)
        (let caller_scope my_scope
          ...))))
Unfortunately, the ... can see the binding of my_scope. You might be able to get around this with a gensym.

---

"I'm still not convinced I fully understand why eval and fn each add an indirection to caller_scope ."

(Now to tackle eval.)

Are you sure 'eval adds a layer of indirection? I think the only indirection you're encountering is caused by 'fn. I just tried it out in Wart to make sure:

  ((fn () ((fn () (caller_scope = caller_scope)))))
  => ''1
  ((fn () ((fn () (caller_scope = (eval 'caller_scope caller_scope))))))
  => nil
  ((fn () ((fn () (caller_scope = ((fn () (eval 'caller_scope caller_scope))))))))
  => ''1
The first ((fn () ...)) layer establishes a binding for caller_scope, but it's an empty environment. The second ((fn () ...)) layer establishes another binding for caller_scope, so now caller_scope is bound to a first-class environment with a single variable in it named "caller_scope".

Within this second layer, we get a true value for (caller_scope = caller_scope). This is just a sanity check.

We false value for (caller_scope = (eval 'caller_scope caller_scope)), presumably because we're comparing the two different environments we've established.

Finally, it seems we can successfully use ((fn () (eval '<foo> caller_scope)))) in place of <foo>, because although it introduces a new environment, it evals in the old one.

Here's another way we can verify that eval doesn't introduce its own binding for caller_scope:

  ((fn () (eval 'caller_scope caller_scope)))
  020lookup.cc:28 no binding for caller_scope
  => nil
  ((fn () (eval '2 caller_scope)))
  => 2
This all seems reasonable to me.

---

"But I needed it to expand to this:"

  let $old push
    mac (push x s)
      `(if (isa stack ,s)
         (push ,x (rep ,s))
         (,$old ,x ,s))
I think you almost got it there. What you really needed was this:

  mac (push x s) :case `(isa stack ,s)
    `(push ,x (rep ,s))
  
  -->
  
  let $old push
    mac (push x s)
      `(if ,`(isa stack ,s)
         ,`(push ,x (rep ,s))
         (,$old ,x ,s))
The difference is the addition of two groups of ,` heiroglyphics.

In between the , and the ` you have "room to operate on the names before starting on the backquote":

  mac (foo n) :case (let a .. `(isa foo-type ,a))
    with (a ..
          b ..)
      `(bar ,a ,b)
  
  -->
  
  let $old foo
    mac (foo n)
      `(if ,(let a .. `(isa foo-type ,n))
         ,(with (a ..
                 b ..)
            `(bar ,a ,b))
         (,$old ,n))
Since this approach would work for Arc-style (compile time) macros, I think it's what you were really looking for. Your "final expansion" branches on run time information before generating the code, so it's specific to fexprs.

---

"But even if there isn't, I'm starting to question if lisp is indeed the hundred-year programming model. I'm starting to feel at the end of my tether, at the limits of what macroexpansion can cleanly support."

False alarm! I'm pretty sure this was just a case of being rusty at one's own programming language. ^_-



1 point by akkartik 3861 days ago | link

Thanks rocketnia! I'm still trying to get mac to generate the comma-backquote expressions, but I'll report back.

(Thanks for finding the typo in foo!)

-----

1 point by akkartik 3854 days ago | link

I've been playing with this for a few days, and I'm still stuck on how to modify this decl:

  (def foo (a) `(list ,a))
so that:

  arc> (foo 3)
  (list 3)
instead returns (list ,3). How can you generate a literal unquote? Perhaps this is a bug in wart's core? Perhaps it's impossible to do if commas don't expand to unquote?

-----

2 points by rocketnia 3852 days ago | link

Sorry, that toy example is too small for me. I don't understand why this macro redefinition approach would lead to that snag (although it does look like a plausible snag to end up in ^_^ ). Could you give some more context?

---

"Perhaps it's impossible to do if commas don't expand to unquote ?"

From what you're saying, it sounds like you're using some custom data structures, but that you just haven't given yourself all the primitives you need to manipulate them.

  > foo <- (make-unquote 12)
  ,12
  
  > (unquote? foo)
  t
  
  > (unquote-get foo)
  12
  
  > foo <- (make-quasiquote 12)
  `12
  
  > (quasiquote? foo)
  t
  
  > (quasiquote-get foo)
  12
Pardon the pseudo-Wart, pseudo-Arc, pseudo-Scheme. XD

-----

1 point by akkartik 3852 days ago | link

Ah, yes I can manipulate commas like that. I was wondering if there's a quasiquote/unquote idiom to handle them :)

-----

1 point by akkartik 3846 days ago | link

Ok, I think I've found a testcase proving that putting the if within backquotes won't work.

  # Simplified expansion of:
  #   mac (foo x)
  #     `(+ ,x 1)
  #
  #   mac (foo x) :case nil
  #     `(list ,car.x)

  mac (foo x)
    `(if nil
       ,`(list ,(car x))
       ,`(+ ,x 1))

  (foo 3)
The problem here is that both branches of the if will be macroexpanded, throwing an unnecessary error for the call to car.

So it seems we do need the if to be outside backquotes, and my subsequent hacks :/ Do you see any holes in this argument?

-----

2 points by rocketnia 3846 days ago | link

First of all, I'm noticing your expansion is suspicious in a couple of ways.

The case itself should be a piece of generated code:

  -#   mac (foo x) :case nil
  +#   mac (foo x) :case `nil
  
  -  `(if nil
  +  `(if ,`nil
I made this same change back in http://arclanguage.org/item?id=18050 where I said "What you really needed was this." It might have looked like I was only talking about the expansion result, but the usage syntax changes too.

Also, this seems to be unrelated to the issue you're having, but I just realized you need to be careful about variable scope. What if the original definition said "mac (foo x)" but the extension said "mac (foo y)"? I recommend putting in a form that binds the right variables:

  -  `(if ,`nil
  +  `(if ,(with (x x)
  +          `nil)
  -    ,`(list ,(car x))
  +    ,(with (x x)
  +       `(list ,(car x)))
       ,`(+ ,x 1))
(Careful though, because this (with ...) form will add a layer of indirection to caller_scope.)

---

To address the issue you're encountering, yes, every case will expand at every call site, even if there's no intuitive need for it to expand there. It can't branch on a decision that hasn't been made until run time, unless you're willing to use the full power of fexprs. (I assume you're trying to maintain a semblance of stage separation, or you wouldn't distinguish macros from functions.)

Keeping this stuff in mind, you can write your code so that it doesn't give you that error:

  # Simplified expansion of:
  #   mac (foo x)
  #     `(+ ,x 1)
  #
  #   mac (foo x) :case `nil
  #     if acons.x
  #       `(list ,car.x)
  #       `(err "I hope this code will never be reached.")

  mac (foo x)
    `(if ,(with (x x)
            `nil)
       ,(with (x x)
          (if (acons x)
           `(list ,(car x))
           `(err "I hope this code will never be reached.")))
       ,`(+ ,x 1))

-----

2 points by rocketnia 3846 days ago | link

"What if the original definition said "mac (foo x)" but the extension said "mac (foo y)"? I recommend putting in a form that binds the right variables"

Whoops, that (with ...) stuff isn't ideal because it leaves the outer variables in scope. That is, if the original definition has a parameter called "list", an extension will see that binding even if it uses a different name for the same parameter, so it might try to call the global 'list and be surprised.

I suppose I recommend doing this instead, where g123-args is a gensym:

  mac (foo . g123-args)
    `(if ,(apply (fn (x)
                   `nil)
                 g123-args)
       ,(apply (fn (x)
                 (if (acons x)
                   `(list ,(car x))
                   `(err "I hope this code will never be reached.")))
               g123-args)
       ,(apply (fn (x)
                 `(+ ,x 1))
               g123-args))
Er, and there's one more change I should probably make here so you can actually use this for what you're trying to do.

  let g1-old foo
    (mac (foo . g2-args)
      `(if ,(apply (fn (x)
                     `nil)
                   g2-args)
         ,(apply (fn (x)
                   (if (acons x)
                     `(list ,(car x))
                     `(err "I hope this code will never be reached.")))
                 g2-args)
         
         (,g1-old ,@g2-args)
         ; or in Arc, ",(apply rep.g1-old g2-args)"
         
         )

-----