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))))))
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
My thanks to rntz for help with 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")