The basic operations on
pairs—pair,
head,
and
tail—can
be used to construct list structure and to select parts
from list structure, but they are incapable of modifying list
structure. The same is true of the list operations we have used so
far, such as append and
list, since these can be defined in terms of
pair,
head,
and
tail.
To modify list structures we need new operations.
The primitive mutators for pairs are
set_head
and
set_tail.
The function set_head
takes two arguments, the first of which must be a pair. It modifies this
pair, replacing the
head
pointer by a pointer to the second argument of
set_head.
ペアに対するプリミティブなミューテータは、
set_head
と
set_tail
です。
関数set_head
は2つの引数を取り、最初の引数はペアでなければなりません。この関数はそのペアを変更し、
head
ポインタをset_headの第2引数へのポインタで置き換えます。
As an example, suppose that x is bound to
list(list("a", "b"), "c", "d")
and y to
list("e", "f")
as illustrated in
figure 3.24.
Evaluating the expression
set_head(x, y)
modifies the pair to which x is bound,
replacing its
head
by the value of y. The result of the operation
is shown in
figure 3.26.
The structure x has been modified and
is now equivalent to
list(list("e", "f"), "c", "d").
The pairs representing the list
list("a", "b"),
identified by the pointer that was replaced, are now detached from the
original structure.
with x and y
bound to the original lists of
figure 3.24.
The
name
z is now bound to a
new pair created by the
pair
operation; the list to which x is bound is
unchanged.
The
set_tail
operation is similar to
set_head.
The only difference is that the
tail
pointer of the pair, rather than the
head
pointer, is replaced. The effect of executing
set_tail(x, y)
on the lists of
figure 3.24
is shown in
figure 3.30.
Here the
tail
pointer of
x has been replaced by the pointer to
list("e", "f").
Also, the list
list("c", "d"),
which used to be the
tail
of x, is now detached from the structure.
The function pair
builds new list structure by creating new pairs,
whereas set_head
and
set_tail
modify existing pairs.
Indeed, we could
implement
pair
in terms of the two mutators, together with a
function
get_new_pair,
which returns a new pair that is not part of any existing list structure.
We obtain the new pair, set its
head
and
tail
pointers to the designated objects, and return the new pair as the result of
the
pair.
function append(x, y) {
return is_null(x)
? y
: pair(head(x), append(tail(x), y));
}
The function append
forms a new list by successively
adjoining the elements of x
to the front of
y.
The
function
append_mutator
is similar to append, but it is a mutator
rather than a constructor. It appends the lists by splicing them together,
modifying the final pair of x so that its
tail
is now y. (It is an error to call
append_mutator
with an empty x.)
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
function make_cycle(x) {
set_tail(last_pair(x), x);
return x;
}
Draw a box-and-pointer diagram that shows the structure
z created by
次の式で作られる構造zのボックスとポインタの図を描いてください。
const z = make_cycle(list("a", "b", "c"));
What happens if we try to compute
last_pair(z)?
last_pair(z)
を計算しようとすると何が起こるでしょうか?
(provided by GitHub user jonathantorres)
If we try to compute last_pair(z), the
program will enter in a infinite loop, since the end of the list points back
to the beginning.
The following
function
is quite useful, although obscure:
以下の
関数
は、わかりにくいですがとても便利です。
function mystery(x) {
function loop(x, y) {
if (is_null(x)) {
return y;
} else {
const temp = tail(x);
set_tail(x, y);
return loop(temp, x);
}
}
return loop(x, null);
}
The function loop
uses the temporary
name
temp
to hold the old value of the
tail
of x, since the
set_tail
on the next line destroys the
tail.
Explain what mystery does in general. Suppose
v is defined by
We mentioned in section 3.1.3 the
theoretical issues of
sameness and change
raised by the introduction of assignment. These issues arise in practice
when individual pairs are shared among different data objects.
For example, consider the structure formed by
As shown in
figure 3.32,
z1 is a pair whose
head
and
tail
both point to the same pair x. This sharing
of x by the
head
and
tail
of z1 is a consequence of the straightforward
way in which
pair
is implemented. In general, using
pair
to construct lists will result in an interlinked structure of pairs in
which many individual pairs are shared by many different structures.
図 3.32に
示すように、z1は
head
と
tail
の両方が同じペアxを指しているペアです。z1の
head
と
tail
によるxのこの共有は、
pair
の素直な実装の結果です。一般に、
pair
を使ってリストを構築すると、多くの個々のペアが多くの異なる構造で共有される、相互にリンクされたペアの構造になります。
When thought of as a list, z1 and
z2 both represent the same list:
リストとして考えると、z1とz2はどちらも同じリストを表しています。
list(list("a", "b"), "a", "b")
In general, sharing is completely undetectable if we operate on lists using
only
pair,
head,
and
tail.
However, if we allow mutators on list structure, sharing becomes
significant. As an example of the difference that sharing can make,
consider the following
function,
which modifies the
head
of the structure to which it is applied:
一般に、リストに対して
pair、
head、
tail
だけを使って操作する限り、共有は完全に検出できません。
しかし、リスト構造に対するミューテータを許すと、共有が重要になります。共有がもたらす違いの例として、適用先の構造の
head
を変更する以下の
関数
を考えてみましょう。
function set_to_wow(x) {
set_head(head(x), "wow");
return x;
}
Even though z1 and
z2 are the same structure,
applying
set_to_wow
to them yields different results. With z1,
altering the
head
also changes the
tail,
because in z1 the
head
and the
tail
are the same pair. With z2, the
head
and
tail
are distinct, so
set_to_wow
modifies only the
head:
z1とz2は同じ構造であるにもかかわらず、
set_to_wow
を適用すると異なる結果が得られます。z1では、
head
を変更すると
tail
も変わります。なぜなら、z1では
head
と
tail
が同じペアだからです。z2では、
head
と
tail
は別々なので、
set_to_wow
は
head
だけを変更します。
z1;
[["a", ["b", null]], ["a", ["b", null]]]
set_to_wow(z1);
[["wow", ["b", null]], ["wow", ["b", null]]]
z2;
>
[["a", ["b", null]], ["a", ["b", null]]]
set_to_wow(z2);
[["wow", ["b", null]], ["a", ["b", null]]]
One way to detect sharing in list structures is to use the
primitive predicate ===,
which we introduced in
section 1.1.6 to test whether two numbers
are equal
and extended in section 2.3.1 to test whether
two strings are equal. When applied to two nonprimitive values,
x === y
tests whether x and
y are the same object (that is, whether
x and y
are equal as pointers).
As will be seen in the following sections, we can exploit sharing to
greatly extend the repertoire of data structures that can be
represented by pairs. On the other hand, sharing can also be
dangerous, since modifications made to structures will also affect
other structures that happen to share the modified parts. The mutation
operations
set_head
and
set_tail
should be used with care; unless we have a good understanding of how our
data objects are shared, mutation can have unanticipated
results.
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
Ben Bitdiddle decides to write a
function
to count the number of pairs in any list structure.
It's easy, he reasons. The number of pairs in
any structure is the number in the
head
plus the number in the
tail
plus one more to count the current pair. So Ben writes the following
function
Ben Bitdiddle は、任意のリスト構造に含まれるペアの数を数える
関数
を書くことにしました。簡単だよと彼は考えます。任意の構造に含まれるペアの数は、
head
に含まれる数に
tail
に含まれる数を足して、現在のペアをカウントするためにさらに1を足せばいい。そこで Ben は以下の
関数
を書きました。
Show that this
function
is not correct. In particular, draw box-and-pointer diagrams representing
list structures made up of exactly three pairs for which Ben's
function
would return 3; return 4; return 7; never return at all.
Devise a correct version of the
count_pairs
function
of exercise 3.16 that returns the number of
distinct pairs in any structure. (Hint: Traverse the structure, maintaining
an auxiliary data structure that is used to keep track of which pairs have
already been counted.)
Write a
function
that examines a list and
determines whether it contains a cycle, that is,
whether a program that tried to find the end of the list by taking
successive
tails
would go into an infinite loop. Exercise 3.13
constructed such lists.
Define a fast pointer and a slow pointer. The fast pointer goes forward
2 steps every time, while the slow pointer goes forward 1 step every time.
If there is a cycle in the list, the fast pointer will eventually catch
up with the slow pointer.
function pair(x, y) {
function dispatch(m) {
return m === "head"
? x
: m === "tail"
? y
: error(m, "undefined operation -- pair");
}
return dispatch;
}
function head(z) { return z("head"); }
function tail(z) { return z("tail"); }
The same observation is true for mutable data. We can implement
mutable data objects as
functions
using assignment and local state. For instance, we can extend the above
pair implementation to handle
set_head
and
set_tail
in a manner analogous to the way we implemented bank accounts using
make_account
in section 3.1.1:
function pair(x, y) {
function set_x(v) { x = v; }
function set_y(v) { y = v; }
return m => m === "head"
? x
: m === "tail"
? y
: m === "set_head"
? set_x
: m === "set_tail"
? set_y
: error(m, "undefined operation -- pair");
}
function head(z) { return z("head"); }
function tail(z) { return z("tail"); }
function set_head(z, new_value) {
z("set_head")(new_value);
return z;
}
function set_tail(z, new_value) {
z("set_tail")(new_value);
return z;
}
Assignment is all that is needed, theoretically, to account for the
behavior of mutable data. As soon as we admit
assignment
to our language, we raise all the issues, not only of assignment, but of
mutable data in general.
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
We see from this that mutation operations on
lists can create garbage that is not part of any accessible
structure. We will see in section 5.3.2 that
JavaScript
memory-management systems include a
garbage collector, which identifies and recycles the memory
space used by unneeded pairs.
The two pairs are distinct
because each call to pair
returns a new pair. The strings are
the same in the sense
that they are primitive data (just like numbers) that are composed of
the same characters in the same order. Since JavaScript provides no way
to mutate a string, any sharing that the designers of a JavaScript
interpreter might decide to implement for strings is undetectable.
We consider primitive data such as numbers, booleans, and strings
to be identical if and only if they are
indistinguishable.
The subtleties of dealing with sharing of mutable data
objects reflect the underlying issues of sameness and
change that were raised in
section 3.1.3. We mentioned there
that admitting change to our language requires that a compound object must
have an identity that is something different from the pieces
from which it is composed. In
JavaScript,
we consider this identity to be the quality that is tested by
===,
i.e., by equality of pointers. Since in most
JavaScript
implementations a pointer is essentially a memory address, we are
solving the problem of defining the identity of objects by
stipulating that a data object itself is the information
stored in some particular set of memory locations in the computer. This
suffices for simple
JavaScript
programs, but is hardly a general way to resolve the issue of
sameness in computational models.
On the other hand, from the viewpoint of
implementation, assignment requires us to modify the environment, which is
itself a mutable data structure. Thus, assignment and mutation are
equipotent: Each can be implemented in terms of the other.