1.1.4 複合手続き
ここまでで、強力なプログラミング言語であれば必ず持っているようないくつかの要素について、それがLispにもあることを見てきました。
- 数値は基本データで、算術演算は基本手続きである。
- 組み合わせをネストすることで、演算を組み合わせることができる。
- 定義は名前と値を関連づけ、抽象化のためにある程度役に立つ。
ここでは、手続きの定義(procedure definition)について学びます。これははるかに強力な抽象化のテクニックで、複合演算に名前をつけ、それにひとつの単位として参照できるようにするというものです。
まずは、“二乗の計算”とはどのように表現できるかを考えてみましょう。例えば、“何かを二乗するには、その何かにその何か自身をかける”のようになるでしょう。これは、私たちの言語では次のように表すことができます。
(define (square x) (* x x))
これは、次のように理解できます。
(define (square x) (* x x))
| | | | | |
定義 二乗する xを かける xを xで.
ここでは、複合手続き(compound procedure)を作り、それにという名前をつけています。この手続きは、何かにそれ自身をかけるという演算を表しています。かける数にはという名前をつけていますが、これは自然言語で代名詞が果たすのと同じ役割を果たしています。この定義を評価すると、この複合手続きを作成し、それをという名前と関連づけています。12
手続き定義の一般形式は以下の通りです。
(define (⟨名前⟩ ⟨仮引数⟩)
⟨本体⟩)
⟨名前⟩は、環境の中で手続きに関連づける記号です。13⟨仮引数⟩は、手続きの本体の中で対応する引数を参照するために使う名前です。⟨本体⟩は、その中に出てくる仮引数をその手続きが適用される実際の引数で置き換えた場合に、手続き適用後の値を返すような式です。14⟨名前⟩と⟨仮引数⟩は、定義する手続きを実際に呼び出すときと同じように、括弧でくくります。
を定義したので、もうそれを使うことができます。
(square 21)
441
(square (+ 2 5))
49
(square (square 3))
81
は、ほかの手続きを定義するための構成部品として使うこともできます。例えば、 は次のように表現できます。
(+ (square x) (square y))
二つの数値が引数として与えられたときにその二乗の和を求めるという手続きも、簡単に定義できます。
(define (sum-of-squares x y)
(+ (square x) (square y)))
(sum-of-squares 3 4)
25
これで、をさらに別の手続きの構成部品として使うこともできるようになります。
(define (f a)
(sum-of-squares (+ a 1) (* a 2)))
(f 5)
136
複合手続きは、基本手続きとまったく同じように使うことができます。実際、上に書いたの定義を見ても、がやのような組み込み手続きなのか、複合手続きとして定義されたものなのか、見分けることはできないはずです。
12. ここで、二つの異なる操作が組み合わされていることに気をつけてください。まず手続きを作成して、それにという名前をつけています。手続きを名前をつけずに作るということと、すでに作られている手続きに名前をつけることということの、二つの概念を区別することは可能であり、重要なことでもあります。そのやり方については1.3.2節で見ていきます。 ↩
13. この本全体を通して、式の一般的な構文について記述する際には、山括弧でくくったイタリックの記号---例えば、⟨name⟩---を使って、それらの式を実際に使うときに埋めなければならない“スロット”を表します。 ↩
14. もう少し一般化な言い方をすると、手続きの本体は式の列にもなりえます。その場合、インタプリタは列のそれぞれの式を順番に評価し、最後の式の値を手続き適用の値として返します。 ↩