[ some of this needs to be moved up: ]
Conceptually, all Scheme objects are allocated on the heap, and referred to via pointers. This actually makes life simple, because you don't have to worry about whether you should dereference a pointer when you want to use a value--you always do. Since pointer dereferencing is uniform, procedures always dereference a pointer to a value when they really use the value, and you never have to explicitly force the dereferencing.
For example, the predefined Scheme procedure +
takes two pointers
to numbers, and automatically dereferences both pointers before doing the
addition. It returns a pointer to the number that's the result of
the addition.
So when we evaluate the expression (+ 2 3)
to add two to three, we are
taking a pointer to the integer 2
and a pointer to integer 3
,
and passing those as arguments to the procedure +
. +
returns
a pointer to the integer 5
. We can nest expressions,
e.g., (* (+ 2 3) 6)
, so that the pointer to five is passed,
in turn, to the procedure *
. Since these functions all accept
pointers as arguments and return pointers as values, you can just ignore
the pointers, and write arithmetic expressions the way you would in
any other language.
When you think about it, it doesn't make any sense to change
the value of an integer, in a mathematical sense. For example,
what would it mean to change the integer 6
's value to be
7
? It wouldn't mean anything sensible, for sure. 6
is a unique, abstract mathematical object that doesn't have any state
that can be changed---6
is 6
, and behaves like 6
,
forever.
What's going on in conventional programming languages is not really changing the value of an integer--it's replacing one (copy of an) integer value with (a copy of) another. That's because most programming languages have both pointer semantics (for pointer variables) and value semantics (for nonpointer variables, like integers). You make multiple copies of values, and then clobber the copies when you perform an assignment.
In Scheme, we don't need to clobber the value of an integer, because we get the effect we want by replacing pointers with other pointers. An integer in Scheme is a unique entity, just as it is in mathematics. We don't have multiple copies of a particular number, just multiple references to it. (Actually, Scheme's treatment of numbers is not quite this simple and pretty, for efficiency reasons I'll explain later, but it's close.)
As we'll see later, an implementation is free to optimize away these pointers if it doesn't affect the programmer's view of things--but when you're trying to understand a program, you should always think of values as pointers to objects.
The uniform use of pointers makes lots of things simpler. In C or Pascal,
you have to be careful whether you're dealing with a raw value or a pointer.
If you have a pointer and you need the actual value, you have to explictly
dereference the pointer (e.g., with C's prefix operator *
, or Pascal's
postfix operator ^
).
If you have a value and you need a pointer to it, you have to take its
address (e.g., with C's prefix &
operator, or Pascal's prefix operator
^
).
In Scheme, none of that mess is necessary. User-defined routines pass
pointers around, consistently, and when they bottom out into predefined
routines (like the built-in +
procedure or set!
special
form) those low-level built-in operations do any dereferencing that's
necessary.
(Of course, when traversing lists and the like, the programmer has to ask for pointers to be dereferenced, but from the programmer's point of view, that just means grabbing another pointer value out of a field of an object you already have a pointer to.)
It is sometimes said that languages like Scheme (and Lisp, Smalltalk, Eiffel, and Java) "don't have pointers." It's at least as reasonable to say that the opposite is true--everything's a pointer. What they don't have is a distinction between pointers and nonpointers that you have to worry about.(2)