Arc Forumnew | comments | leaders | submitlogin
Dynamic Binding
6 points by nlavine 6084 days ago | 5 comments
Here's a macro that does dynamic binding. It transforms any given fn-expression, so it can work on custom functions, let, or with. No withs support yet, though.

Usage:

  (= x 1)
  (def foo () (+ x 5))
  (foo) => 6
  (let x 7 (foo)) => 6
  (fluid:let x 7 (foo)) => 12
Code:

  ; uniq-args-tree: returns a tree with uniq's for all the   symbols that will
  ; actually be bound in the environment corresponding to the args tree (with 'o's)
  (def uniq-args-tree (x)
    (if (acons x) (if (is (car x) 'o) (cons 'o (uniq-args-tree (cadr x)))
                      (cons (uniq-args-tree (car x))
                            (uniq-args-tree (cdr x))))
        x (uniq)
        t nil))

  ; flatten-argsyms: flatten an argument list, but return only
  ; the symbols which will be bound in the new environment (without 'o's)
  (def flatten-argsyms (argtree)
    ((rfn rec (tree tail)
       (if (acons tree) (if (is (car tree) 'o) (rec (cadr tree) tail)
                            (rec (car tree) (rec (cdr tree) tail)))
           tree         (cons tree tail)
           t            tail))
      argtree nil))

  (def interleave (xs ys)
    (if (no xs) ys
        (cons (car xs) (interleave ys (cdr xs)))))

  ; value-pairs: for setting or with'ing
  ; allows destructuring sets and with-expansions with the same mechanism
  (def binding-pairs (places vals)
    (interleave (flatten-argsyms places) (flatten-argsyms vals)))

  ; fn-transform: tried it as a macro, but this way seems cleaner
  (def fn-transform (form proc)
    (let ex (macex form)
      (if (no:acons ex) ex
          (acons (car ex)) (cons (fn-transform (car ex) proc) (cdr ex))
          (no:is (car ex) 'fn) ex
          (apply proc (cdr ex))))) ; (cdr ex) chops off the 'fn symbol

  ; fluid: dynamic binding
  ; use like (fluid:let x 1 (foo))
  (mac fluid (form)
    (fn-transform form
      (fn (args . body)
        (withs (at1 (uniq-args-tree args)
                at2 (uniq-args-tree args))
         `(fn ,at1
            (with ,(binding-pairs at2 args)
              (set ,@(binding-pairs args at1))
              (do1
                ,@body
                (set ,@(binding-pairs args at2)))))))))
I tried to write it so it would leave some useful helper functions around.

I'd be interested in comments on the implementation and the approach. I'm not sure yet if this is the right way to do dynamic binding.



3 points by almkglor 6084 days ago | link

nice ^^. Finally someone else is also using the "foo: modifier" approach for macros.

-----

1 point by nlavine 6084 days ago | link

It seems like the right way to go. And hopefully the fn-transformer function will make it easier for others to do the same thing : ].

-----

2 points by CatDancer 6084 days ago | link

See my comment in http://smuglispweeny.blogspot.com/2008/02/faux-dynamic-bindi... for my discussion of how to get dynamic binding to work in the presence of threads and exceptions.

-----

1 point by nlavine 6082 days ago | link

Parameters look interesting, but the man page suggests that there's a pretty significant overhead in code tree nodes to using them. Do you think we can get that functionality cheaper?

The thing I like about my approach is the simplicity - everything expands to sets. If we had parameters, we'd probably want to modify set so that it worked on them. In that case, this approach would still work, and would be the most idiomatic way to do it.

It also separates functionality nicely, since it would work with any thread mechanism that allowed per-thread variables (all of them). Because it only expands to set calls, it's a general mechanism for dynamic binding, and the thread stuff should "just work" if they're used together.

-----

1 point by CatDancer 6082 days ago | link

We can't know what is "significant overhead" without testing. Arc already has its own overhead, so for all we know the added overhead of using parameters might be utterly insignificant. Or not.

Certainly a layered approach is a good idea: an underlying mechanism that provides bug-free dynamic binding functionality, and then a macro layer that allows people to use the functionality in a convenient and easy way.

You might be able to use set with MzScheme's thread local variables if you also used something like using "protect" to restore values on exceptions; but you wouldn't be able to use set with MzScheme's parameters because you need to specify what is the scope that you want the parameter to have a new dynamic value within.

The key challenge to using MzScheme's parameters is that you'd need a way to have Arc compile a variable reference "foo" into a Scheme parameter call "(foo)". However if someone did that I expect the result would be simpler because we wouldn't need the code to set and restore the values ourselves.

-----