Chapter 3. 関数

Table of Contents
単純な抽象としての関数
関数のアリティ (arity)
関数インターフェイス
関数呼び出しの評価
再帰関数
再帰関数呼び出しの評価
例: コインチェンジ遊び
末尾呼出と末尾再帰
例: エイト・クイーンパズル
相互再帰関数
相互に定義された末尾再帰
無環境関数とクロージャ関数
高階関数
例: 二分探索遊び
例: 高階関数パズル
カリー化とアンカリー化

関数はプログラミングの基盤です。 理論上、関数を使わずループを使ってプログラムを作ることは可能です。 けれどそのようなプログラミングスタイルは現実的ではないでしょう。 ATS は他の言語と同様に for ループや while ループを直接実装できます。 けれども私としては、再帰呼出もしくはよりよい末尾再帰呼出によって、 プログラマにループを実装してほしいと考えています。 このプログラミングスタイルは ATS のより進んだプログラミングの特徴とうまく調和できるからです。 そのような特徴はより先のページで紹介します。

この章に出てくるコードとテストのための追加コードは オンライン から得られます。

単純な抽象としての関数

double 型である式が与えられた時、 その式を自分自身と掛け合わせることで二乗を計算することができます。 もしその式が複雑な式であるなら、その評価を一度きりにするためにその式に名前を付けることができます。 例えば次のような例です:

let val x = 3.14 * (10.0 - 1.0 / 1.4142) in x * x end

二乗を取るもっと良い方法が見つかったと想像してみましょう。 この方法の恩恵を受けるには、 プログラム中の二乗しているコードをそれぞれ修正しなければなりません。 このようなプログラミングスタイルは明らかにモジュラリティを欠いています。 この問題に対処するために、 次のような関数を実装することで任意の浮動小数点数の二乗を計算できます:

fn square (x: double): double = x * x

fn キーワードは再帰をしない関数を定義を開始します。 そしてキーワードに続く名前は関数名です。 先の例の場合、 square 関数は x という名前の引数を一つ取ります。 この引数の型は double で、返値の型も double です。 シンボル = の右辺 (right-hand side - RHS) の式が関数の中身です。 この場合は x * x になります。 二乗を取るより良い方法が見つかったら、 square 関数の中身を再実装するだけで済みます。 それ以外の修正は不要です。 (二乗を取るときには単に square を呼び出しているからです。)

square が名前だとすれば、それが参照する式とは何なのでしょうか? 上の関数定義は次のように書くこともできることがわかります:

val square = lam (x: double): double => x * x

ここで = 記号の右辺はラムダ式で、double 型の引数を1つ取り、double 型の値を返す無名関数を表わしています。 そして => 記号に続く式が関数の本体です。 もし望むなら、関数の引数名を次のように変更することができます:

val square = lam (y: double): double => y * y

これは (関数の引数の) α-変換と呼ばれ、 新しくできたラムダ式は元の式に対するα-同値と呼ばれます。

ラムダ式は (関数の) 値です。 2引数の関数を表わすラムダ式があるとします。 (T1, T2) -> T の型をラムダ式に割り当てるためには、 関数の2つの引数が型 T1 と T2 を持つと仮定した時、 関数の本体が型 T であるかどうか証明しなければなりません。 適用する箇所を変更すると、 より少ないもしくはより多い引数を取る関数を表わすラムダ式になります。 例えば、ラムダ式 lam (x: double): double => x * x は関数の型 (double) -> double を割り当てることができます。 この型は double -> double と書くこともできます。

exp は関数の型 (T1, T2) -> T の式であると仮定します。 exp は名前を持つ関数やラムダ式である必要はないことに注意してください。 式 exp1 と exp2 に型 T1 と T2 が割り当て可能だとすると、 関数適用 exp(exp1, exp2) には型 T を割り当てることができます (この関数適用も関数呼び出しであるかもしれません)。 より少ないもしくはより多い引数の関数適用の型付けも同様です。

前に定義した関数 square を使った例を見てみましょう。 2つの円から成り立つリングの境界線は同じ点を中心としています。 外側と内側の円の半径をそれぞれ R と r とすると、 リングの面積は次の関数 area_of_ring で計算できます:

fn area_of_ring (R: double, r: double): double = 3.1416 * (square(R) - square(r)) // end of [area_of_ring]

浮動小数点数の減算と乗算の関数が型 (double, double) -> double で、 square が型 (double) -> double で与えられた時、 area_of_ring の本体に型 double を割り当てられることは、単純な方法で検査できます。