Cat’s hacks:

dyn

Dynamic binding that works with exceptions, continuations, and threads

dyn1.arc

(mac dynvar (name (o init))
  (w/uniq (param val)
    `(withs (,val ,init
             ,param (ac-scheme (make-parameter ,val)))
       (defvar ,name ,param))))

(mac dynamic (var value . body)
  (w/uniq (param v f)
    `(with (,param (defvar-impl ,var)
            ,v ,value
            ,f (fn () ,@body))
       (ac-scheme (parameterize ((,param ,v)) (,f))))))

description

MzScheme has a feature called parameters which implements a form of dynamic binding. What’s cool about MzScheme’s implementation is that it works correctly in the presence of exceptions, continuations, and threads, which flummox more naive implementations of dynamic binding.

Arc uses these MzScheme parameters for stdin and stdout, which is why tostring can locally reassign stdout to be going to a string, but that doesn’t mess up other threads printing to their stdout at the same time.

This hack provides a light wrapper around MzScheme parameters, allowing dynamic variables to be declared and used in Arc.

The dynvar macro declares a dynamic variable, with an optional initial value:

  arc> (dynvar a 3)
  nil
  arc> (def foo () a)
  #<procedure: foo>
  arc> (foo)
  3

Much like a macro has to be defined before it can be used, a dynamic variable has to be declared before it’s used:

  arc> (def foo () a)
  #<procedure: foo>
  arc> (dynvar a 3)
  nil
  arc> (foo)
  #<primitive:parameter-procedure>

Here we see that foo wasn’t able to get the value of the dynamic variable (instead getting the underlying MzScheme implementation of the variable), because foo was defined before a.

The dynamic form causes the value of the dynamic variable to be changed during the execution of the dynamic. This is not a lexical scope, since the change affects any called function that uses the variable:

  arc> (dynvar a 3)
  nil
  arc> (def foo () a)
  #<procedure: foo>
  arc> (dynamic a 5 (foo))
  5

Outside the dynamic, the variable still has its original value:

  arc> a
  3

The value of the variable can be set while inside the dynamic, and that change will be visible to other functions being called while inside the dynamic, but the change won’t affect the value of the dynamic variable outside:

 arc> (dynvar a 3)
 nil
 arc> (def foo () (++ a))
 #<procedure: foo>
 arc> (dynamic a 10
        (prn (foo))
        (prn (foo))
        (prn (foo))
        nil)
 11
 12
 13
 nil
 arc> a
 3

Being “outside” the dynamic includes not only exiting the dynamic normally or with an exception, but also being outside the dynamic with continuations or threads:

 arc> (dynvar a 3)
 nil
 arc> (def foo () (++ a))
 #<procedure: foo>
 arc> (do (thread (repeat 10
                     (prn "thread: " a)
                     (sleep 1)))
          (dynamic a 5
            (repeat 5
              (prn (foo))
              (sleep 2))))
 6
 thread: 3
 thread: 3
 7
 thread: 3
 thread: 3
 8
 thread: 3
 thread: 3
 9
 thread: 3
 thread: 3
 10
 thread: 3
 thread: 3
 nil

thanks

My thanks to rntz for help with this hack!

prerequisites

get this hack

wget http://ycombinator.com/arc/arc3.tar
tar xf arc3.tar
cd arc3
wget -O - http://hacks.catdancer.ws/ac0.patch | patch
wget -O - http://hacks.catdancer.ws/defvar0.patch | patch
wget http://hacks.catdancer.ws/ac0.arc
wget http://hacks.catdancer.ws/defvar0.arc
wget http://hacks.catdancer.ws/dyn1.arc
mzscheme -m -f as.scm
(load "ac0.arc")
(load "defvar0.arc")
(load "dyn1.arc")

comment

Comment in the Arc Forum.

license

public domain