First Class EnvironmentsPicoLisp uses dynamic binding for symbolic variables. This means that the value of a symbol is determined by the current runtime context, not by the lexical context in the source file.
This has advantages in practical programming. It allows you to write independent code fragments as data which can be passed to other parts of the program to be later executed as code in that context.
For example, the major part of the PicoLisp GUI framework consists of code snippets, which are installed as event handlers, button actions or update managers in GUI components.
In a lexically scoped language, you can't separate code from its environment. A call like (foo X Y) has no meaning by itself (unless X and Y are treated as "free" variables, i.e. dynamically, not lexically). Instead, you must write a let or lambda expression, or pass around full function definitions as closures. All this is much more noisy (consider the typical case of dozens of such fragments in a single GUI page).
PicoLisp tries to be as dynamic as possible. This involves also (and much more important than just variable bindings) things like I/O-channels, 'make' environments, the place of the currently executed method in the inheritance hierarchy (for 'super' and 'extra') or the current 'This' object. A running code fragment can always assume to have access to these environments.
This article is only concerned about variable bindings.
First Class Data TypeIn programming languages, a first class data type can be created and manipulated separately within the language, independent from its intended purpose.
For a function, for example, the intended purpose is to call it. If it is a first class function, it can be build, passed around etc., independent from that. The two tasks (creating and calling the function) can be handled independently within the language.
For an environment, the intended purpose is to
- activate it
- execute one or more expressions in it
- deactivate it, possibly restoring the previous environment
First Class EnvironmentsTo make such an environment useful, it is important that the three tasks (creation, activation/deactivation and execution) can be controlled independently.
Let us consider the expression (foo X Y) again. Assume we want to execute it in an environment where X is 3 and Y is 4. The direct way is
(let (X 3 Y 4) (foo X Y) )This handles the three tasks all at once. It creates and activates the environment, immediately executes the code, and then deactivates the environment.
If the expression (foo X Y) is supplied from the application code, stand-alone in one part of the application, and is supposed to run in some other part of the application in an environment active there at that moment, it is also no problem. It can simply be executed:
(foo X Y)If, however, that application environment supposed to be manipulated separately, for example because it is passed over across a HTTP transaction or appears in an RPC call, then the
- creation of that environment is typically done in the application
- activation of that environment is done in the GUI framework
- execution of the expression(s) is done in the application
- deactivation is done in the GUI framework again
CreationThe environment is represented by a list of symbol-value pairs. It can be created with the direct, explicit lisp operations, or more conveniently with the 'env' function.
If the environment is intended as a subset of the current environment, i.e. if you simply want to create it with the current values of X and Y, you may write
: (setq Env (env '(X Y))) -> ((Y . 4) (X . 3))Otherwise, you may pass explit values
: (setq Env (env 'X 3 'Y 4)) -> ((Y . 4) (X . 3))
ActivationThe simplest way to activate an environment would be to to iterate over the list and 'set' each symbol to its value. This is normally not recommended, because it would not restore the previous environment when done.
In most cases either 'bind' or 'job' are used. The main difference between these two functions is that job modifies the environment destructively, to store modified values before restoring the previous environment.
: (bind Env (* X Y)) -> 12 : (job Env (* X Y)) -> 12If the environment is to be modified by the expression, use 'job':
: Env -> ((Y . 4) (X . 3)) : (job Env (* X (inc 'Y))) -> 15 : Env -> ((Y . 5) (X . 3))These examples don't show the separation of activation and execution, as this cannot be simply done in an isolated example. For a real-world use case, take a look at the top-level GUI function in "@lib/form.l". The relevant part can be reduced to
(with "*App" # Point 'This' to the current form (job (: env) # Activate environment of that form (<post> ... (<hidden> ...) ... (if *PRG (let gui ... (htPrin "Prg") ) # Execute the GUI code ... (let gui ... (htPrin "Prg") ) # Execute the GUI codeThe GUI code which actually builds the page is in "Prg". When it runs, This and the desired environment (from the form's env property) are properly activated.
A similar case can be found more down in "@lib/form.l" in 'postGui' to handle HTTP POST and XMLHttpRequest events.