SICP arrow designation is slightly overloaded. I will quote the relevant part of the text to understand this diagram.
The object of the procedure is a couple whose code indicates that the procedure has one formal parameter, namely x, and the body of the procedure (* xx). Part of the environment in the procedure is a pointer to the global environment, since it is the environment in which the lambda expression was calculated to create the procedure. A new binding has been added to the global frame, which links the procedure object to the square of the symbol. In the general case, a definition creates definitions by adding bindings to frames.
So, analyze each arrow.
"global env" → square. This arrow, apparently, simply denotes a square as a symbol of the global environment. It is noteworthy that this environment is the only frame of the stack, since define was called in the global environment.
"square" → two points. This arrow seems to state that all of these two points are stored in the name "square" , which is located in the global environment.
left point → "parameters" / "body". This arrow indicates that the left point is an “object” that is thought to hold two pieces of data, a “list of formal parameters” and a “body of a procedure”.
right point → square. This arrow indicates that the right dot contains a "pointer" back to the global environment.
This diagram gives a high-performance POV on how characters output a value in Lisp. In particular, the character is “evaluated” in a particular “context”. Context is a linked list of "environment frames", each of which contains a certain set of names → value mappings. To evaluate a character, this linked list follows and returns the first value that is displayed from the character name. A diagrammatic example might be
"foo" → { "bar" : 3 → { "foo" : 8 } → { "foo" : 10 } , "baz" : 4 }
where the score foo returns 8 by "skipping" the first frame and finding the value 8 in the second frame while ignoring the third frame. This ignore function is important --- it assumes that in some contexts there may be names that are shadow values from larger contexts.
So, the whole picture here points to the following:
Finally, we need to talk about what it means to value lambda. To evaluate lambda, you must pass it a list of values. He uses this list of input values and compares them with the formal list of parameters that he stores to create a new environment frame that displays the formal parameters for entering values. It then calculates the body of the lambda using this new frame as the main frame and the associated frame as the subsequent context. Graphically, let's say square looked like
+--- Formal parameter list / +--- Body of function | | (left: (x) (* xx)) (right: {global frame})
Then, when we evaluate it as (square 3) , we create a new frame using 3 and a list of formal parameters
{ "x" : 3 }
and appreciate the body. First we find the name * . Since this is not in our new local frame, we must find it in the global frame.
"*" → { "x" : 3 } → { global frame }
It turns out that the definition of multiplication exists and exists. So we need to pass some values so that we look at the "x"
"x" → { "x" : 3 } → { global frame }
since x is stored in a local frame, we find it there and pass 3 and 3 as arguments to the multiplication function we found.
The important part is that the local frame obscures the global frame. This means that if x also made sense in the global frame, we would redefine it in the context of evaluating the body of square .
Finally, since I was asked to answer this question in the context of questions about what the meaning of a variable means, it is important to note that the above is a very specific implementation of the very specific semantics of variables. On its surface, you can always say that "variables in lisp mean that this process is happening." This can be a bit of a challenge, however.
Another semantics of the word "variable" (one of which I and most mathematics prefer) is the idea that a variable in a context denotes a certain fixed, but unknown value in a domain. If we look at the definition of lambda in the body of square
(lambda (x) (* xx))
we see that this is more or less implied semantics of this phrase --- when interpreting (* xx) we see that x as some value value (for example, a number), but one that we don’t know anything about. When interpreting (lambda (x) (* xx)) we see that in order to understand the meaning of the phrase inside the lambda, we must give it the value x . This is roughly the standard semantics of variables and functions used everywhere.
The problem is that the stack frame implementation described here is also configured to easily violate this semantics - in fact, in this example it is very subtle. To be specific: define breaks semantics. The reason is obvious in the following code snippet
(define foo 3) foo (define foo 4) foo
In this fragment, we evaluate each phrase sequentially and see that the variable foo (supposedly "fixed, but unknown") changes from line 2 to line 4. This is due to the fact that define allows us to edit the stack frame that lives in the context, and not just creating a new context that shadows the old, for example, lambda . This means that we should consider the variables as not “fixed, but unknown”, but instead a series of mutable slots that cannot be guaranteed while maintaining their value over time - a much more complex semantics, which, perhaps, should make us call foo an "slot" or "assignable".
We can also see this as a simple abstraction. We would like the variables to have standard “fixed but unknown” semantics, but due to the stack frame mechanism and define behavior, we do not fully adhere to this value.
As a final note, Lisps often provide you with a form called let , which can be used to replicate the previous example without discarding the semantics of the variables:
(let ((foo 3)) foo (let ((foo 4)) foo) foo)
In this case, foo in line 2 takes the value 3 , line foo in line 4 exists in another context of the variable and thus only the shadow foo in line 2 ... and therefore takes a different fixed value 4 , finally, line foo in line 5 is again identical to line foo in line 2 and takes the same value.
In other words, let allows us to create arbitrary local contexts (coincidentally, creating new stack frames backstage, as you might expect). The golden rule that allows us to know that semantics of semantics is safe is called a slightly incorrect α-transformation. This rule states that if you rename a variable everywhere and evenly within the same context, then the value of the program does not change.
Thus, the previous example: α-transformation, identical in meaning to this
(let ((foo 3)) foo (let ((bar 4)) bar) foo)
and perhaps a little less confusing as we no longer have to worry about the effects of the shading foo .
So, can we make lisp define semantics more secure? View. You can imagine the following transformation:
- Disable circular dependencies in define sets, for example.
(define xy) (define yx) not allowed, but (define x 3) (define yx) is not. - Move all
define to the very beginning of any given context (stack frame) and place them in the dependency order. - Make the "re
define " error of any variable
It turns out that this conversion is a bit complicated (the code movement is tough and therefore can be cyclical dependent), but if you fix some small problems, you will see that in any context a variable can take only one fixed value, but an unknown value.
You will also find the following: - any program of the following, transformed form
(define x ... definition of x ...) (define y ... definition of y ...) (define z ... definition of z ...) ... body ...
equivalent to the following
(let ((x ... definition of x ...)) (let ((y ... definition of y ...)) (let ((z ... definition of z ...)) ... body ...)))
which is another way to show that our pretty, simple semantics of the variable are "variable as fixed but unknown."