Re: define and assignment
- From: int2k@xxxxxxx
- Date: 5 Aug 2006 20:51:59 -0700
"H." <hbe123@xxxxxxxxx> writes:
Most of this explanation looks spot on, but I'm not sure about these
beginning lines:
--8<---------------cut here---------------start------------->8---
(define a 1) => Obj *a;
a = new Int(1);
(define b a) => Obj *b;
b = a;
(set! b 2) => b = new Int(2)
The reason this part puzzles me thought symbols in Scheme were like
Java primitives, in that they are always copied by valued (as opposed
to boxed things like Objs, in which a new arrow is drawn pointing to
the object).
Symbols are never copied. Symbols in Lisp are just that, symbols,
things that have names. They don't have a value by themselves.[1]
It's the evaluation rule in Lisp that says that when an expression is
a symbol, the value currently associated with that symbol is to be
fetched from the environment. This is slightly different from saying
"the symbol has this value" because if the symbol was e. g. garbage
collected, the value would continue to exist if some other object
still had a reference to that value. That's why I said symbols can be
thought of as pointers: changing the value of a pointer doesn't change
the object at the memory location the pointer previously pointed at.
The reason I think the model works this way is because of the EnvDraw
program in UCB STk, which draws environment models as the user types
statements. Here is what it shows:
(define a 1)
a ----> 1
(define b a)
a -----> 1
b -----> 1
a and b point to separate things, so it appears the value is simply
just copied. So I am wondering if the model is more like:
int a;
a=1
b=a
But I could be wrong; someone should speak up one way or another. If
I'm wrong, then this environment modeler program we use in class seems
a bit misleading!
I'm not sure what the EnvDraw program is supposed to do exactly, but
int a;
a=1
b=a
is not what's happening. I'm going to try to give a more detailed
explanation of how I understand things. At least conceptually,
variables in Lisp work like this. You have
1. Memory, the place where your objects live
2. Memory locations: every object is stored at a specific location in
memory. Memory locations are the addresses of objects.
3. The environment: the environment maps symbols to memory
locations. The environment could be implemented as a hash table
where the keys are symbol and the value associated with a
particular key is a stack of memory locations.
4. Symbols: symbols are basically just things that have names.
Among other things, they serve as keys for the environment.
When you do a (define a expr) then the implementation does something
like
this:
1. If nothing is associated with the symbol a (the key), a new stack
of memory locations is created and stored there.
2. The memory location of the "1" is stored.
In some imperative pseudo language, it might look like this:
void define(symbol sym, expression expr, environment env)
{
if (not env.has_key(sym)) {
env[sym] = new LocationStack();
}
loc = eval(expr, env);
env[sym].push(loc);
}
When a symbol is evaluated, the evaluation rule in Lisp says that a
look-up in the environment has to be performed:
location lookup(symbol sym, environment env)
{
return env[sym].top(loc);
}
When you do a (set! a expr), the location on top of the stack for that
symbol is set to a new value:
location set!(symbol sym, expression expr, environment env)
{
location loc;
if (not env.has_key(sym)) {
raise Error("Symbols's value is void: " + sym.name())
}
loc = eval(expr, env);
env[sym].pop();
env[sym].push(loc);
}
You're probably wondering what the stack of locations is about.
Through this, variables can have different scopes. Consider this
form:
(let ((a "outer"))
(princ a) ; => prints "outer"
(let ((a "inner"))
(set! a "foo"))
(princ a)) ; => still prints "outer"
This kind of behaviour could be achieved like this:
location let(list binding_forms, list body, environment env)
{
list form;
symbol sym;
location loc, ret_val;
expression expr;
// Push the new bindings onto the env
for form in binding_forms {
sym = form[0];
expr = form[1];
loc = eval(expr, env);
if (not env.has_key(sym)) {
env[sym] = new LocationStack();
}
env[sym].push(loc);
}
// Evaluate the expressions in the body
ret_val = NIL;
for expr in body {
ret_val = eval(expr, env);
}
// Pop the new bindings from the env
for form in binding_forms {
sym = form[0];
env[sym].pop();
}
return ret_val;
}
I hope my pseudo-code made sense to you. Conceptually, I think this
explanation is pretty accurate. Of course, actual implementations
might work differently. E. g. integers (fixnums in Lisp speak) might
in fact be immediate (i. e. unboxed) values. Also, there may not be
an actual environment object that gets passed around all the time. A
compiler can figure out a lot of this statically.
Cheers
Wolfram
Footnotes:
[1] Actually, this is not quite true, because e. g. in Common Lisp,
symbols do have so-called function and value slots (c. f.
<http://www.lisp.org/HyperSpec/Body/acc_symbol-value.html>). But
that's more of an an implementation detail. Also, these slots only
hold the value of *dynamic* variables (which Scheme doesn't have), not
of *lexical* variables (local variables in a function).
.
- Follow-Ups:
- Re: define and assignment
- From: H.
- Re: define and assignment
- References:
- define and assignment
- From: Tamas K Papp
- Re: define and assignment
- From: Marcin 'Qrczak' Kowalczyk
- Re: define and assignment
- From: Tamas K Papp
- Re: define and assignment
- From: int2k
- Re: define and assignment
- From: H.
- define and assignment
- Prev by Date: Re: why scheme over perl or python?
- Next by Date: Re: define and assignment
- Previous by thread: Re: define and assignment
- Next by thread: Re: define and assignment
- Index(es):
Relevant Pages
|