We now come to the decisive step of mathematical abstraction: we
forget about what the symbols stand for. … [The mathematician]
need not be idle; there are many operations which he may carry out
with these symbols, without ever having to look at the things they
stand for.
いまや数学的抽象の決定的な一歩に至る。記号が何を表しているかを忘れるのだ。
… [数学者は]何もせずにいる必要はない。記号が表すものを見ることなく、
それらの記号を用いて実行できる操作はたくさんあるのだから。
Hermann WeylThe Mathematical Way of Thinking
We concentrated in chapter
[1] on computational processes and on the
role of
functions
in program design. We saw how to use primitive data (numbers) and primitive
operations (arithmetic operations), how to combine
functions
to form compound
functions
through composition, conditionals, and the use of parameters, and how to
abstract
processes
by using
function declarations.
We saw that a
function
can be regarded as a pattern for the local evolution of a process, and we
classified, reasoned about, and performed simple algorithmic analyses of
some common patterns for processes as embodied in
functions.
We also saw that higher-order
functions
enhance the power of our language by enabling us to manipulate, and thereby
to reason in terms of, general methods of computation. This is much of the
essence of programming.
第
[1]章では、計算プロセスと、プログラム設計における関数の役割に焦点を当てました。プリミティブなデータ(数値)とプリミティブな演算(算術演算)の使い方、合成・条件式・パラメータの使用を通じて関数を組み合わせて複合関数を作る方法、そして関数宣言を使ってプロセスを抽象化する方法を見てきました。関数はプロセスの局所的な発展のパターンとみなせることを学び、関数に具現化されたプロセスの一般的なパターンを分類し、推論し、簡単なアルゴリズム解析を行いました。また、高階関数が計算の一般的な手法を操作し、それに基づいて推論する能力を与えることで、言語の力を高めることも見てきました。これがプログラミングの本質の多くを占めています。
In this chapter we are going to look at more complex data. All the
functions
in chapter
[1] operate on simple numerical data, and simple data are
not sufficient for many of the problems we wish to address using
computation. Programs are typically designed to model complex phenomena,
and more often than not one must construct computational objects that have
several parts in order to model real-world phenomena that have several
aspects. Thus, whereas our focus in chapter
[1] was on building
abstractions by combining
functions
to form compound
functions,
we turn in this chapter to another key aspect of any programming language:
the means it provides for building abstractions by combining data objects
to form
compound data.
この章では、より複雑なデータに目を向けます。第
[1]章の関数はすべて単純な数値データを扱うものでしたが、計算で取り組みたい問題の多くには、単純なデータでは不十分です。プログラムは通常、複雑な現象をモデル化するために設計されるものであり、現実世界の複数の側面を持つ現象をモデル化するには、複数の部分を持つ計算オブジェクトを構築しなければならないことがほとんどです。したがって、第
[1]章では関数を組み合わせて複合関数を作ることによる抽象の構築に焦点を当てましたが、この章ではプログラミング言語のもう一つの重要な側面に目を向けます。すなわち、データオブジェクトを組み合わせて
複合データを構成することによる抽象の構築手段です。
Why do we want compound data in a programming language? For the same
reasons that we want compound
functions:
to elevate the conceptual level at which we can design our programs, to
increase the modularity of our designs, and to enhance the expressive power
of our language. Just as the ability to
declare functions
enables us to deal with processes at a higher conceptual level than that of
the primitive operations of the language, the ability to construct compound
data objects enables us to deal with data at a higher conceptual level than
that of the primitive data objects of the language.
なぜプログラミング言語に複合データが必要なのでしょうか? それは複合関数が必要なのと同じ理由です。プログラムを設計する際の概念レベルを高め、設計のモジュール性を向上させ、言語の表現力を高めるためです。関数を宣言する能力が、言語のプリミティブな演算よりも高い概念レベルでプロセスを扱うことを可能にするのと同じように、複合データオブジェクトを構築する能力は、言語のプリミティブなデータオブジェクトよりも高い概念レベルでデータを扱うことを可能にします。
Consider the task of designing a system to perform
arithmetic with rational
numbers. We could imagine an operation
add_rat
that takes two rational numbers and produces their sum. In terms of
simple data, a rational number can be thought of as two integers: a
numerator and a denominator. Thus, we could design a program in which
each rational number would be represented by two integers (a numerator
and a denominator) and where
add_rat
would be implemented by two
functions
(one producing the numerator of the sum and one producing
the denominator). But this would be awkward, because we would then
need to explicitly keep track of which numerators corresponded to
which denominators. In a system intended to perform many operations
on many rational numbers, such bookkeeping details would clutter the
programs substantially, to say nothing of what they would do to our
minds. It would be much better if we could glue together
a numerator and denominator to form a pair—a compound data
object—that our programs could manipulate in a way that would
be consistent with regarding a rational number as a single conceptual
unit.
有理数の算術演算を行うシステムを設計する課題を考えてみましょう。2つの有理数を受け取ってその和を返す演算add_ratを想像できます。単純なデータの観点からは、有理数は2つの整数、すなわち分子と分母で考えることができます。したがって、各有理数を2つの整数(分子と分母)で表現し、add_ratを2つの関数(一方が和の分子を、もう一方が和の分母を生成する)で実装するプログラムを設計できるでしょう。しかしこれは厄介です。どの分子がどの分母に対応するかを明示的に追跡し続けなければならないからです。多くの有理数に対して多くの演算を行うシステムでは、このような管理の詳細がプログラムを大幅に複雑にし、私たちの頭の中も混乱させるでしょう。分子と分母をくっつけて
ペアを作る——つまり複合データオブジェクトを作る——方がはるかによいでしょう。そうすれば、有理数を単一の概念的な単位とみなす方法でプログラムが操作できます。
The use of compound data also enables us to increase the modularity of
our programs. If we can manipulate rational numbers directly as
objects in their own right, then we can separate the part of our
program that deals with rational numbers per se from the details of
how rational numbers may be represented as pairs of integers. The
general technique of isolating the parts of a program that deal with
how data objects are represented from the parts of a program that deal
with how data objects are used is a powerful design methodology called
data abstraction. We will see how data abstraction makes
programs much easier to design, maintain, and modify.
複合データを使うことで、プログラムのモジュール性を高めることもできます。有理数をそれ自体のオブジェクトとして直接操作できれば、有理数そのものを扱うプログラムの部分と、有理数が整数のペアとしてどのように表現されるかという詳細を分離できます。データオブジェクトがどのように表現されるかを扱うプログラムの部分と、データオブジェクトがどのように使用されるかを扱うプログラムの部分を分離する一般的な手法は、
データ抽象と呼ばれる強力な設計方法論です。データ抽象がプログラムの設計、保守、変更をいかに容易にするかを見ていきましょう。
The use of compound data leads to a real increase in the expressive power
of our programming language. Consider the idea of forming a
linear combination
$ax+by$. We
might like to write a
function
that would accept $a$,
$b$, $x$, and
$y$ as arguments and return the value of
$ax+by$. This presents no difficulty if the
arguments are to be numbers, because we can readily
declare the function
複合データを使うことで、プログラミング言語の表現力が実質的に向上します。一次結合
$ax+by$を作るという考えについて考えてみましょう。$a$、$b$、$x$、$y$を引数として受け取り、$ax+by$の値を返す関数を書きたいとします。引数が数値であれば、簡単に関数を宣言できます。
function linear_combination(a, b, x, y) {
return a * x + b * y;
}
But suppose we are not concerned only with numbers. Suppose we would like to
describe a process that forms
linear combinations whenever addition and multiplication are
defined—for rational numbers, complex numbers, polynomials, or
whatever. We could express this as a
function
of the form
しかし、数値だけを扱うのではないとしたらどうでしょう。加算と乗算が定義されている場合——有理数、複素数、多項式、その他何であれ——に一次結合を形成するプロセスを記述することができたらどうでしょう。これは次のような形の関数で表現できるでしょう。
function linear_combination(a, b, x, y) {
return add(mul(a, x), mul(b, y));
}
where add and mul
are not the primitive
functions
+ and * but rather
more complex things that will perform the appropriate operations for
whatever kinds of data we pass in as the arguments
a, b,
x, and y. The key
point is that the only thing
linear_combination
should need to know about a,
b, x, and
y is that the
functions
add and mul will
perform the appropriate manipulations. From the perspective of the
function
linear_combination,
it is irrelevant what a,
b, x, and
y are and even more irrelevant how they might
happen to be represented in terms of more primitive data. This same example
shows why it is important that our programming language provide the ability
to manipulate compound objects directly: Without this, there is no way for a
function
such as
linear_combination
to pass its arguments along to add and
mul without having to know their detailed
structure.
ここでaddとmulは、プリミティブな関数である+や*ではなく、引数a、b、x、yとしてどのような種類のデータを渡しても適切な演算を行う、より複雑なものです。重要なのは、linear_combinationがa、b、x、yについて知る必要があるのは、関数addとmulが適切な操作を行うということだけだという点です。関数linear_combinationの観点からは、a、b、x、yが何であるかは無関係であり、それらがより基本的なデータでどのように表現されているかはさらに無関係です。この同じ例は、プログラミング言語が複合オブジェクトを直接操作する能力を提供することがなぜ重要かを示しています。この能力がなければ、linear_combinationのような関数が、引数の詳細な構造を知ることなくaddやmulに引数を渡す方法がありません。
We begin this chapter by implementing the rational-number arithmetic system
mentioned above. This will form the background for our discussion of
compound data and data abstraction. As with compound
functions,
the main issue to be addressed is that of abstraction as a technique for
coping with complexity, and we will see how data abstraction enables us to
erect suitable
abstraction barriers
between different parts of a program.
この章では、上で述べた有理数の算術演算システムを実装するところから始めます。これが複合データとデータ抽象についての議論の基礎となります。複合関数の場合と同様に、取り組むべき主な問題は、複雑さに対処する手法としての抽象であり、データ抽象がプログラムの異なる部分の間に適切な
抽象バリアを設けることを可能にすることを見ていきます。
We will see that the key to forming compound data is that a programming
language should provide some kind of
glue
so that data
objects can be combined to form more complex data objects. There are
many possible kinds of glue. Indeed, we will discover how to form compound
data using no special
data
operations at all, only
functions.
This will further blur the distinction between
function
and
data,
which was already becoming tenuous toward the end
of chapter
[1]. We will also explore some conventional techniques for
representing sequences and trees. One key idea in dealing with compound
data is the notion of
closure—that the
glue we use for combining data objects should allow us to combine not only
primitive data objects, but compound data objects as well. Another key idea
is that compound data objects can serve as
conventional interfaces for combining program modules in
mix-and-match ways. We illustrate some of these ideas by presenting a
simple graphics language that exploits closure.
複合データを構成する鍵は、プログラミング言語がデータオブジェクトを組み合わせてより複雑なデータオブジェクトを作るための何らかの
接着剤
を提供することだとわかるでしょう。接着剤にはさまざまな種類が考えられます。実際、特別な
データ
操作を一切使わず、関数だけで複合データを構成する方法を発見します。これにより、第
[1]章の終わりにはすでに薄れつつあった
関数
と
データ
の区別がさらに曖昧になります。また、シーケンスやツリーを表現する従来の手法も探求します。複合データを扱う上での一つの重要な概念は、
閉包性の考え方です——データオブジェクトを組み合わせる接着剤は、プリミティブなデータオブジェクトだけでなく、複合データオブジェクトも組み合わせられるべきだということです。もう一つの重要な概念は、複合データオブジェクトがプログラムモジュールを自在に組み合わせるための
規約インターフェースとして機能できるということです。閉包性を活用したシンプルなグラフィックス言語を示すことで、これらの概念の一部を説明します。
We will then augment the representational power of our language by
introducing
symbolic expressions—data whose elementary parts
can be arbitrary symbols rather than only numbers. We explore various
alternatives for representing sets of objects. We will find that,
just as a given numerical function can be computed by many different
computational processes, there are many ways in which a given data
structure can be represented in terms of simpler objects, and the
choice of representation can have significant impact on the time and
space requirements of processes that manipulate the data. We will
investigate these ideas in the context of symbolic differentiation,
the representation of sets, and the encoding of information.
次に、
シンボリック式——基本的な部分が数値だけでなく任意のシンボルでありうるデータ——を導入することで、言語の表現力を拡張します。オブジェクトの集合を表現するさまざまな方法を探求します。ある数値関数が多くの異なる計算プロセスで計算できるのと同じように、あるデータ構造をより単純なオブジェクトで表現する方法は多数あり、表現の選択がデータを操作するプロセスの時間と空間の要件に大きな影響を与えうることがわかるでしょう。これらの概念を、シンボリック微分、集合の表現、情報の符号化の文脈で調べていきます。
Next we will take up the problem of working with data that may be
represented differently by different parts of a program. This leads
to the need to implement
generic operations, which must handle many different types of data.
Maintaining modularity in the presence of generic operations requires more
powerful abstraction barriers than can be erected with simple data
abstraction alone. In particular, we introduce data-directed
programming as a technique that allows individual data representations
to be designed in isolation and then combined
additively (i.e., without modification). To illustrate the power
of this approach to system design, we close the chapter by applying what we
have learned to the implementation of a package for performing symbolic
arithmetic on polynomials, in which the coefficients of the polynomials can
be integers, rational numbers, complex numbers, and even other polynomials.
次に、プログラムの異なる部分によって異なる方法で表現されうるデータを扱う問題に取り組みます。これは、多くの異なる型のデータを扱わなければならない
ジェネリック演算を実装する必要性につながります。ジェネリック演算がある中でモジュール性を維持するには、単純なデータ抽象だけで設けられるものよりも強力な抽象バリアが必要です。特に、個々のデータ表現を独立して設計し、その後
加法的に(つまり変更なしに)組み合わせることを可能にする手法として、データ指向プログラミングを導入します。このシステム設計アプローチの力を示すために、この章の最後に、学んだことを多項式のシンボリック算術を行うパッケージの実装に応用します。そこでは多項式の係数が整数、有理数、複素数、さらには他の多項式であることもできます。