I had the pleasure of meeting the amazing Alan Perlis and
talking with him a few times, when I was still a student. He and
I had in common a deep love and respect for two very different
programming languages: Lisp and APL. Following in his footsteps
is a daunting task, even though he blazed an excellent
trail. Still, I would like to reexamine one comment he made in
the original foreword to this book (and, please, I suggest that
you read his foreword, which immediately follows this one,
before you finish this one). Is it really true that it is better
to have 100 functions operate on one data structure than to have
10 functions operate on 10 data structures?
まだ学生だった頃、素晴らしいアラン・パーリスに会い、何度か話をする喜びに恵まれました。彼と私には、Lisp と APL という2つのまったく異なるプログラミング言語に対する深い愛情と敬意が共通していました。彼が見事に切り開いた道を辿るのは大変なことですが、本書の元の序文で彼が述べたある発言を再検討してみたいと思います(どうか、この序文を読み終える前に、すぐ後に続く彼の序文を読むことをお勧めします)。10のデータ構造に10の関数を操作させるより、1つのデータ構造に100の関数を操作させる方が本当に良いのでしょうか?
To answer that question carefully, we first need to ask whether
that one data structure is universal
: can it
conveniently fulfill the roles of those 10 more specialized data
structures?
この問いに慎重に答えるには、まずその1つのデータ構造が万能
であるかどうかを問う必要があります。10のより専門的なデータ構造の役割を便利に果たせるでしょうか?
For that matter, we can also ask: do we really need 100
functions? Is there a single universal function that can fulfill
the roles of all those other functions?
さらに言えば、こうも問えます。本当に100の関数が必要でしょうか?他のすべての関数の役割を果たせる単一の万能関数は存在するのでしょうか?
The surprising answer to that last question is
yes
; it is only slightly tricky to construct a
function that accepts (1) a data structure that serves as a
description of some other function, and (2) a list of arguments,
and behaves exactly as that other function would when applied to
the given arguments. And it is only slightly tricky to design a
data structure capable of describing any computation
whatsoever. One such data structure (the tagged-list
representation of expressions and statements, paired with
environments that associate names with values) and one such
universal function (apply)
are described in Chapter 4 of this book. So maybe we need only
one function and one data structure.
この最後の問いに対する驚くべき答えははい
です。(1) ある関数の記述として機能するデータ構造と、(2) 引数のリストを受け取り、与えられた引数に適用されたときにその関数とまったく同じように振る舞う関数を構築するのは、ほんの少しトリッキーなだけです。そして、あらゆる計算を記述できるデータ構造を設計するのも、ほんの少しトリッキーなだけです。そのようなデータ構造の一つ(式と文のタグ付きリスト表現と、名前を値に関連付ける環境の組み合わせ)と、そのような万能関数の一つ(apply)は、本書の第4章で説明されます。つまり、必要なのは1つの関数と1つのデータ構造だけかもしれません。
That is true in theory. In practice, we find it convenient to
draw distinctions that help us, as human beings constructing
descriptions of computations, to organize the structure of our
code so that we can better understand them. I believe that
Perlis was making a remark not about computational capability,
but about human abilities and human limitations.
理論的にはその通りです。しかし実践的には、計算の記述を構築する人間として、コードの構造をよりよく理解できるように整理するために、区別を設けることが便利だと感じます。パーリスが述べていたのは、計算能力についてではなく、人間の能力と人間の限界についてだったと私は考えています。
One thing the human mind seems to do well is to name things; we
have powerful associative memories. Given a name, we can quickly
recall some associated thing to mind. This is why we typically
find it easier to work with the lambda calculus than the
combinatory calculus; it is much easier for most people to
interpret the Lisp expression
(lambda (x) (lambda (y) (+ x y)))
or the JavaScript expression
x => y => x + y
than the combinatory expression
((S ((S (K S)) ((S ((S (K S)) ((S (K K)) (K +)))) ((S (K K)) I)))) (K I))
even though there is a direct structural correspondence, easily
expressed in five lines of Lisp code.
人間の心がうまくやれることの一つは、ものに名前を付けることです。私たちは強力な連想記憶を持っています。名前が与えられれば、関連するものをすぐに思い出すことができます。これが、組み合わせ計算(コンビナトリー計算)よりもラムダ計算の方が扱いやすいと通常感じる理由です。ほとんどの人にとって、Lisp の式
(lambda (x) (lambda (y) (+ x y)))
や JavaScript の式
x => y => x + y
を解釈する方が、組み合わせ式
((S ((S (K S)) ((S ((S (K S)) ((S (K K)) (K +)))) ((S (K K)) I)))) (K I))
を解釈するよりもはるかに簡単です。Lisp のコード5行で容易に表現できる直接的な構造的対応があるにもかかわらずです。
So while in principle we could get by with just one universal
function, we prefer to modularize our code, to give names to the
various pieces, and to mention the names of function descriptions
rather than constantly feeding the descriptions themselves to the
universal function.
ですから、原理的には1つの万能関数だけでやっていけますが、コードをモジュール化し、さまざまな部品に名前を付け、関数の記述自体を絶えず万能関数に与えるのではなく、記述の名前を使う方を好みます。
In my 1998 talk Growing a Language,
I commented
that a good programmer does not just write programs. A good
programmer builds a working vocabulary.
As we design and
define more and more parts of our programs, we give names to
those parts, and the result is that we have a richer language in
which to write the rest.
1998年の講演Growing a Language
で、良いプログラマは単にプログラムを書くだけではない。良いプログラマは使える語彙を築く
と述べました。プログラムのますます多くの部分を設計し定義するにつれて、それらの部分に名前を付け、その結果、残りを書くためのより豊かな言語を手に入れることになります。
But we also find it natural to draw distinctions among data
structures, and to give them names.
しかし、データ構造の間に区別を設け、それらに名前を付けることも自然なことです。
It may be that nested lists are a universal data structure (and
it is worth noting that many modern and widely used data
structures, such as HTML and XML and JSON, are also
parenthetically nested representations, only slightly more
elaborate than Lisp's bare parentheses). There are also
many functions, such as finding the length of a list or applying
a function to every element of a list and getting back a list of
the results, that are useful in a wide variety of
situations. And yet, when I am thinking about a specific
computation, I often say to myself, This list of two
things I expect to be a personal name and a surname, but that
list of two things I expect to be the real and imaginary parts
of a complex number, and that other list of two things I will
regard as the numerator and denominator of a fraction.
In other words, I draw distinctions—and it may be useful
to represent those distinctions explicitly in the data
structure, in part to prevent mistakes such as accidentally
treating a complex number as a fraction. (Again, this is a
comment about human abilities and human limitations.)
入れ子のリストが万能データ構造である可能性があります(HTML、XML、JSON など、広く使われている現代のデータ構造の多くも括弧で入れ子にされた表現であり、Lisp の素の括弧よりほんの少し手の込んだものにすぎないことは注目に値します)。また、リストの長さを求めたり、リストのすべての要素に関数を適用して結果のリストを得たりするような、さまざまな状況で役立つ関数も多数あります。それでも、特定の計算について考えるとき、私はしばしばこう自分に言います。この2つのもののリストは名前と姓のはずだが、あちらの2つのもののリストは複素数の実部と虚部のはずで、またあちらの2つのもののリストは分数の分子と分母と見なそう。
言い換えれば、私は区別を設けるのです——そして、その区別をデータ構造の中に明示的に表現することが有用かもしれません。その一部は、複素数を誤って分数として扱うような間違いを防ぐためです。(繰り返しますが、これは人間の能力と人間の限界についてのコメントです。)
Since the first edition of this book was written, almost four decades
ago, a lot more ways of organizing data have become relatively
standard, in particular the object-oriented
approach, and many
languages, including JavaScript, support specialized data structures
such as objects and strings and heaps and maps with a variety of
built-in mechanisms and libraries. But in doing so, many languages
abandoned support for more general, universal notions. Java, for
example, originally did not support first-class functions, and has
incorporated them only relatively recently, greatly increasing its
expressive power.
本書の初版が書かれてからおよそ40年の間に、データを整理するさらに多くの方法が比較的標準になりました。特にオブジェクト指向
アプローチがそうであり、JavaScript を含む多くの言語が、さまざまな組み込みメカニズムやライブラリとともに、オブジェクト、文字列、ヒープ、マップなどの特殊なデータ構造をサポートしています。しかし、そうする中で、多くの言語はより一般的で万能的な概念のサポートを放棄しました。例えば Java は当初、第一級関数をサポートしておらず、比較的最近になってようやく取り入れ、表現力を大幅に高めました。
APL, likewise, originally did not support first-class functions, and
moreover its original single data structure—arrays of any number of
dimensions—was not so conveniently useful as a universal data
structure because arrays could not contain other arrays as
elements. More recent versions of APL do support anonymous function
values and nested arrays, and these have made APL dramatically more
expressive. (The original design of APL did have two very good things
going for it: a comprehensive set of functions applicable to that one
data structure, and moreover an extremely well chosen set of names for
those functions. I'm not talking about the funny symbols and Greek
letters, but the spoken words that APL programmers use when mentioning
them, words like
shape,
reshape,
compress,
expand, and
laminate;
these are names not for the
symbols, but for the functions they represent. Ken Iverson had a real
knack for choosing short, memorable, vivid names for functions on
arrays.)
APL も同様に、当初は第一級関数をサポートしておらず、さらにその元々の単一データ構造——任意の次元数の配列——は、配列が他の配列を要素として含むことができなかったため、万能データ構造としてはそれほど便利ではありませんでした。APL の最近のバージョンは無名関数値と入れ子配列をサポートしており、これらにより APL の表現力は劇的に向上しました。(APL の元の設計には2つの非常に良い点がありました。その1つのデータ構造に適用可能な包括的な関数セットと、さらにそれらの関数に対する極めてよく選ばれた名前のセットです。面白い記号やギリシャ文字のことを言っているのではなく、APL プログラマがそれらに言及するときに使う話し言葉——
shape、
reshape、
compress、
expand、
laminate
といった言葉です。これらは記号ではなく、それらが表す関数の名前です。ケン・アイバーソンは、配列に対する関数に短く、記憶に残る、鮮やかな名前を選ぶ本当のセンスを持っていました。)
While JavaScript, like Java, was originally designed with objects and
methods in mind, it also incorporated first-class functions from the
beginning, and it is not difficult to use its objects to define a
universal data structure. As a result, JavaScript is not as distant
from Lisp as you would think, and as this edition of
Structure and Interpretation of Computer Programs
demonstrates, it is a good
alternate framework for presenting the key ideas.
SICP
was never about a programming language; it presents powerful, general
ideas for program organization that ought to be useful in any
language.
JavaScript は Java と同様に当初オブジェクトとメソッドを念頭に設計されましたが、最初から第一級関数も取り入れており、そのオブジェクトを使って万能データ構造を定義することも難しくありません。その結果、JavaScript は思うほど Lisp から遠くはなく、本書「計算機プログラムの構造と解釈」のこの版が示すように、核となるアイデアを提示するための良い代替フレームワークです。SICP はプログラミング言語についての本ではありませんでした。どの言語でも有用であるはずの、プログラム構成のための強力で一般的なアイデアを提示しているのです。
What do Lisp and JavaScript have in common? The ability to abstract a
computation (code plus some associated data) for later execution as a
function; the ability to embed references to such functions within
data structures; the ability to invoke functions on arguments; the
ability to draw a distinction (conditional execution); a convenient
universal data structure; completely automatic storage management for
that data (which seems like a no-brainer, given everything else, until
you realize that many widely used programming languages don't have
it); a large set of useful functions for operating on that universal
data structure; and standard strategies for using the universal data
structure to represent more specialized data structures.
Lisp と JavaScript の共通点は何でしょうか?計算(コードと関連するデータ)を関数として後で実行するために抽象化する能力。そのような関数への参照をデータ構造の中に埋め込む能力。引数に対して関数を呼び出す能力。区別を設ける能力(条件付き実行)。便利な万能データ構造。そのデータの完全に自動的なストレージ管理(他のすべてを考えれば当然のことのように思えますが、広く使われているプログラミング言語の多くがそれを持っていないと気付くまでは)。その万能データ構造を操作するための有用な関数の大きなセット。そして、万能データ構造を使ってより特殊なデータ構造を表現するための標準的な戦略です。
So maybe the truth is somewhere in between the extremes that Perlis so
eloquently posited. Maybe the sweet spot is something more like 40
functions general enough to operate usefully on a universal data
structure such as lists, but also 10 sets of 6 functions each that are
relevant when we take one of 10 specialized views of that universal
data structure. This is manageable if we give good names to these
functions and specialized views.
おそらく真実は、パーリスが雄弁に提起した両極端の間のどこかにあるのでしょう。おそらくスイートスポットは、リストのような万能データ構造に対して有用に操作できるほど一般的な40の関数に加えて、その万能データ構造の10の特殊な見方のそれぞれに関連する6つの関数のセットが10組、というようなものでしょう。これらの関数と特殊な見方に良い名前を付ければ、管理可能です。
As you read this book, please pay attention not only to the
programming language constructs and how they are used, but also to the
names given to functions and variables and data
structures. They are not all as short and vivid as the names Iverson
chose for his APL functions, but they have been chosen in a deliberate
and systematic way to enhance your understanding of the overall
program structure.
本書を読む際には、プログラミング言語の構成要素とその使い方だけでなく、関数や変数やデータ構造に付けられた名前にも注意を払ってください。それらはすべてがアイバーソンがAPLの関数に選んだ名前ほど短く鮮やかではありませんが、プログラム全体の構造の理解を高めるために、意図的かつ体系的に選ばれています。
Primitives, means of combination, functional abstraction, naming, and
conventions for using a universal data structure in specialized ways
by drawing distinctions: these are the fundamental building blocks of
a good programming language. From there, imagination and good
engineering judgment based on experience can do the rest.
プリミティブ、組み合わせの手段、関数的抽象化、名前付け、そして区別を設けることで万能データ構造を特殊な方法で使うための規約——これらが優れたプログラミング言語の基本的な構成要素です。あとは、想像力と経験に基づく優れたエンジニアリング判断がやってくれます。
Guy L. Steele Jr.Lexington, Massachusetts, 2021