I was playing with common lisp a bit and realized that unlike Lisp, where all local variables are either arguments(via lambda) lambda, or via parameters(via let).
In other words, they always follow the IIFE idiom:
((x, y, z) => {
/* I has variables */
})(1, 2, 3);
eg
((lambda (x y z)
; I has variables
) 1 2 3)
or
(let ((x 1)(y 2)(z 3))
; I has variables
)
In JavaScript, vars "feel" like setq, but setq mutate global scope if the local variable is not declared to shadow it, in JavaScript, vars don't mutate global scope no matter what.
Let's say I want to do this in Lisp:
(function() {
var x = 1;
var y = 2;
var z = 3;
/* woo, I has 3 vars */
})();
/*note that the vars no longer exist */
If I try to do this using this:
; progn is like lambda but never has arguments and automatically iifes itself.
; eg (progn (setq x 1)) is ((lambda () (setq x 1))
(progn
(setq x 1)
(setq y 2)
(setq z 3)
; Woo I has 3 vars
)
; oops, I polluted global scope :(
To get this JavaScript-like feel, I end up doing something like
; wait, we're writing smalltalk now?
; [
; | x y z |
; x := 1.
; y := 2.
; z := 3.
; "I has three vars..."
; ] value.
(let
((x)(y)(z))
(setq x 1)
(setq x 2)
(setq x 3)
)
Strangely, Lisp does not seem to have a parallel to a JavaScript var/let/const; in the sense of the example above(Does it? I'm not very familiar with Lisp...).
My question is; where are vars actually stored? They are not passed as arguments and they are not explicitly declared in the parameters... But they must be stored somewhere, and that somewhere is not the global scope...
JavaScript variables are "bindings" on an object called a LexicalEnvironment object. When execution enters a scope that can have its own bindings (such as when entering a function), a new LexicalEnvironment is created and populated with the arguments, locals, and locally-declared functions.
This is a specification construct; how JavaScript engines actually implement it is up to the engine provided it faithfully replicates the semantics of the specification. There is nothing in the specification that lets us directly access the object. (In particular: If nothing closes over the variables, they can happily be implemented on a stack and cleaned up by just resetting the stack pointer on exit.)
Note that this is how closures work in JavaScript, so reading up on closures will give you more insight into where variables are stored. In brief: When you create a function, the function has a (somewhat indirect) reference to the active LexicalEnvironment object as of when it was created. Since the object contains the bindings for the variables, the function can access them via the object.
A specific example will probably help; see comments:
// A function that returns a function that closes over its
// local variable
function f() {
// When this function is called, a *LexicalEnvironment* object
// is created and populated with an `a` variable
// (and a few other things)
var a = Math.random();
// If we create a function, it gets a reference to the object,
// and so it can access that variable
return function() {
return a;
};
}
// Create a lexical environment containing a variable, get back
// a function with access to it
var f1 = f();
// Do it again
var f2 = f();
// Now we have *two* separate lexical environment objects (well,
// more, but two related to `f`). They both continue to exist
// as long as there's something referring to them (like all other
// objects). Our `f1` and `f2` each refer to one of them, so they
// still exist and `f1` and `f2` can use the `a` on each of them:
console.log(f1());
console.log(f2());
// Now we release the functions, which release the lexical
// environment object they had references to
f1 = f2 = undefined;
In Common Lisp assigning a variable with something like setq
does not declare the variable as such. There is no mechanism for such block scope, where mentioning a variable will create it in the inner most block scope.
You mentioned let
and lambda
already.
Note that lambda
has &aux
variables:
(lambda (&aux (x 1) (y 2) (z 3))
; x and y and z are variables here...
)
These auxiliary variables need to be declared in the lambda parameter list, but are not a part of the arguments with which one calls a function.
Example:
CL-USER 61 > ((lambda (x &aux (y (* x x)) (z 12))
(+ y x z))
5)
42
Your example
CL-USER 63 > (let (x y z)
(setq x 1)
(setq y 2)
(setq z 3)
(+ x y z))
6
was a popular option, especially because years ago it was usually written as:
CL-USER 72 > (prog (x y z)
(setq x 1)
(setq y 2)
(setq z 3)
(return (+ x y z)))
6
prog
provides a block, local variables and a tagbody. In a tagbody we can have local tag and jumps via a go to construct.
Summary: This means you can have block local or function local variables, but you have to declare them first in Common Lisp. In some other languages you can do that anywhere in the body of some scope.
When you enter a block, this way, the list of lexical variables is known and fixed. The language implementation does not need to scan the body for new variables and it does not need to provide some way to extend the current lexical environment with new variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With