(mac implicit (name (o val)) `(do (defvar ,name (scheme.make-parameter ,val)) (mac ,(sym (string "w/" name)) (v . body) (w/uniq (param gv gf) `(with (,param (defvar-impl ,',name) ,gv ,v ,gf (fn () ,@body)) (scheme (parameterize ((,param ,gv)) (,gf))))))))
Implicit variables are passed implicitly into function calls, instead of needing to be explicitly listed in the function arguments.
arc> (implicit foo) #(tagged mac #<procedure: w/foo>)
arc> (def h () (string "foo: " foo)) #<procedure: h>
arc> (def g () (h)) #<procedure: g>
arc> (def f () (w/foo 42 (g))) #<procedure: f>
arc> (f) "foo: 42"
They’re useful when, as in this example, the same variable is being passed through several layers of function calls.
Any program that uses implicit variables can be rewritten to pass the arguments explicitly, though you may need to add the argument to many intermediate functions.
arc> (def h (foo) (string "foo: " foo)) #<procedure: h>
arc> (def g (foo) (h foo)) #<procedure: g>
arc> (def f () (g 42)) #<procedure: f>
arc> (f) "foo: 42"
Likewise, any argument that is being passed explicitly can be passed implicitly instead, though the result may be unbearably tedious if the function is often called with arguments that are different.
arc> (implicit a) #(tagged mac #<procedure: w/a>)
arc> (implicit b) #(tagged mac #<procedure: w/b>)
arc> (def plus () (+ a b)) #<procedure: plus>
arc> (w/a 3 (w/b 5 (plus))) 8
Unlike a global variable, setting the value of an implicit variable with its w/
macro doesn’t set its value everywhere, but only inside of the execution of the w/
.
arc> (implicit foo 5) #(tagged mac #<procedure: w/foo>)
arc> (def g () (+ foo 10)) #<procedure: g>
arc> foo 5
arc> (w/foo 10 (g)) 20
arc> foo 5
Being “outside” the w/
macro includes not only exiting the w/
normally or with an exception, but also being outside with continuations or threads:
arc> (implicit foo 10) #(tagged mac #<procedure: w/foo>)
arc> (do (thread (repeat 10 (prn "in thread: " foo) (sleep 1))) (w/foo 5 (repeat 5 (prn "outside thread: " foo) (sleep 2)))) outside thread: 5 in thread: 10 in thread: 10 outside thread: 5 in thread: 10 in thread: 10 in thread: 10 outside thread: 5 in thread: 10 outside thread: 5 in thread: 10 in thread: 10 outside thread: 5 in thread: 10 in thread: 10 nil
You can also set the value of an implicit variable with Arc’s usual assignment forms such as =
and ++
. Such assignments will be visible to callers, but isolated within execution context of its w/
form.
arc> (implicit foo 10) #(tagged mac #<procedure: w/foo>)
arc> (def g () (++ foo)) #<procedure: g>
arc> (w/foo 20 (g) foo) 21
arc> foo 10
I don’t know of an example of where this would be useful, but I didn’t see a reason to forbid it either.
Implicit variables are implemented using MzScheme’s parameter feature. Arc’s stdin
, stdout
, and stderr
are also MzScheme parameters, which is how for example tostring
can be used in one thread without messing up the stdout of another thread.
In MzScheme to get the value of a parameter you call it as a function with no arguments, which is why in Arc to get the current value of stdout you use (stdout)
. This implemention uses defvar so that the value of an implicit variable can be referenced with simply foo
instead of having to say (foo)
.
My thanks to rntz for help with this hack.
The w/foo
macro form is inspired by Arc’s w/stdin
and w/stdout
.
This hack depends on arc3.1, scheme0, and defvar2.
Using the hackinator:
$ hack \ ycombinator.com/arc/arc3.1.tar \ awwx.ws/defarc0.patch \ awwx.ws/defarc-ac0.patch \ awwx.ws/extend0.arc \ awwx.ws/scheme0.arc \ awwx.ws/defvar1.patch \ awwx.ws/defvar2.arc \ awwx.ws/implicit2.arc
Same as Arc.