awwx.ws
The latest version of this hack is implicit2

dyn2

Dynamic binding

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

(mac dlet (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. MzScheme’s implementation works correctly with 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)
#<procedure: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 dlet form causes the value of the dynamic variable to be changed during the execution of the dlet. 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> (dlet a 5 (foo))
5

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

arc> a
3

The value of the variable can be set while inside the dlet, and that change will be visible to other functions being called while inside the dlet, 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> (dlet a 10
       (prn (foo))
       (prn (foo))
       (prn (foo)))
11
12
13
13
arc> a
3

Being “outside” the dlet includes not only exiting the dlet normally or with an exception, but also being outside the dlet 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)))
         (dlet 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

Thank you

My thanks to rntz for help with this hack.

Get this hack

Using the hackinator:

$ hack \
    ycombinator.com/arc/arc3.1.tar \
    awwx.ws/ac0.patch \
    awwx.ws/ac1.arc \
    awwx.ws/defarc0.patch \
    awwx.ws/defvar1.patch \
    awwx.ws/defvar1.arc \
    awwx.ws/dyn2.arc

unLicense

This code is in the public domain.

Contact me

Twitter: awwx
Email: andrew.wilcox [at] gmail.com