💾 Archived View for vierkantor.com › xukut › manual › swail › variable.gmi captured on 2024-05-26 at 14:45:35. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-05-10)
-=-=-=-=-=-=-
Variables in Swail have some features borrowed from Common Lisp that you might not be used to coming from non-Lisps. Namely, apart from lexical variables (as featured in almost all modern programming languages), there are also dynamic variables. The difference between the two is in the scoping rules that determine whether a variable (and which variable) is available: a variable that is lexically scoped is available when the program text using it is surrounded by a binding of that variable, and a dynamically scoped variable is available when the expression binding it hasn't finished its execution.
Lexical variables are denoted by a symbol like `x' or `some-fancy-variable-name'; dynamic variables are denoted by surrounding them with `(dyn _)', as in `(dyn x)' or `(dyn some-fancy-variable-name)'.
A concrete example:
(let (x 1) (do (defn foo () (print x)) (* x is lexically scoped, the surrounding binding is `(let (x 1) ...)', so x evaluates to 1 *) (let (x 2) (foo)))) (let ((dyn x) 1) (defn foo () (print (dyn x))) (* x is dynamically scoped, the `(let ((dyn x) 2) ...)' expression is still being executed, so x evaluates to 2 *) (let ((dyn x) 2) (foo)))
The motivation for having lexical variables is essentially that maths (the λ calculus) has them, and most programming languages have them, so they are clearly useful.
So why use dynamic variables? First of all, they allow us to implement various features in user code that other languages require the implementation to provide. The condition system requires only functions, dynamic variables and continuations, and no specific language support. Second, because they provide certain useful patterns for user code that would be awkward without dynamic variables.
Let's say you have code that does a bunch of work, calls a specific function, then does a bunch more work, and you call this code in a few places. One of the calls is returning slightly wrong results, and you'd like to debug what is going on. But you don't want to run the debugging code multiple times because it's slow, or spews a bunch of unnecessary text onto your screen, or some other reason. So you want the debug info to be output only in the dynamic scope of one specific call, which you can do perfectly using dynamic variables:
(set! (dyn do-debug?) ff) (defn fn-to-be-debugged () (if (dyn do-debug?) (spew-out-debug-info)) ...) (defn my-complicated-code () ... (fn-to-be-debugged) ... (fn-to-be-debugged) ... (let ((dyn do-debug) tt) (fn-to-be-debugged)) ... (fn-to-be-debugged))
You could achieve the same by setting a global variable to `tt' right before the call and to `ff' right after, but that's error-prone: you need to keep the code before the call and after the call completely in sync, and you have to make sure you reset the global to the value it had before the call (suppose the global was already `tt' before the first `set!', then you'd turn it off incorrectly).
Return the value of the dynamic variable `var'.
If `var' is not bound in the dynamic environment, raises a `swail:unbound-dynamic-variable' condition instead.
More information about conditions.
We distinguish two ways of getting a new value from the same variable. By *binding* the variable to a new value, a new *scope* for the variable is activated. While the scope is active, the variable has this value, and when the scope ends, the variable is associated again with the old value (or none). By *setting* the variable to a new value, the scope of the variable is retained. The variable will keep its new value until the end of this previous scope (or until set again).
Scopes are generally hierarchical: if the scope for `a' is active when the scope for `b' is activated, the scope for `a' must remain active for at least as long as the scope for `b'. Thus it makes sense to talk about the *innermost scope*, which is the currently active scope that has been activated most recently, and will end earliest.
Set the variable `var' to `value' and return `value'.
If `var' is not bound, a new binding is created that lasts until the innermost scope ends.
This uses the default syntax for variables: if `var' is a symbol, it refers to a lexical variable. If it is of the form `(swail:dyn var)', it refers to the dynamic variable `var'.
Bind variables while evaluating `expr', returning the result of evaluation.
`bindings' is a list of alternating variables and expressions. Each variable will be bound to the result of evaluating the subsequent expression. These bindings will be active during the evaluation of `expr'.
This is an explanation of low-level design in XukutOS. We hope that the design works well enough that you can stick to higher-level stuff for day to day activities.
The Swail implementation uses an environment object for recording the current variable bindings, which is passed around to all functions. We store the lexical environment as a dynamic variable named `swail:lexical'.
The dynamic environment itself should have the following properties:
* it can be saved/restored through a single register (so no setting all the value fields of symbols)
* it can look up a variable, or throw an error if the variable was not found
* it can bind a (list of) new or existing variable(s) (not modifying previously saved environments)
* it can set an existing variable to a new value, or if the variable was not found modify the environment by setting a new variable to a new value
* it can be introspected, for example by the compiler, to see what bindings exist
The lexical environment should satisfy similar properties, except saving is done by updating a single value in the dynamic environment (rather than a register).
So environments are represented by objects with at least 3 fields:
* lookup: a `fn-oarcc' called like `(lookup env var or-else)' which returns the value of `var' in environment `env' or the result of calling `(or-else var)' if `var' was not found
* set: a `fn-oarcc' called like `(set env var value)' which modifies `env' by setting the value of `var' in environment `env' to `value'
* to-list: a `fn-oarcc' called like `(to-list env)' which returns a binderlist containing all bindings in this environment (potentially including duplicates: the head-wards value is the correct one)
Binding is implemented by creating a new environment object that references the original environment.
These strict requirements on layout and call structure should ensure an efficient implementation.
Optimizing compilers may also decide to store (lexical) variables in other places such as registers or on the stack, as long as they aren't needed in an environment object (for example, when constructing a function, the function needs to close over the variables in the environment).
Any questions? Contact me:
By email at vierkantor@vierkantor.com