English | Japanese SourceForge.JP
Copyright (c) 2011-2012 Yutaka Saito

暗黙的マッピング

実装のきっかけ

ここに、y = x2 という数式のグラフを描画する処理を考えてみます。 座標値は、x に -5 から 5 までの数値を 1 きざみで代入したときの y の値を求め、 各座標値に対応する画面位置にプロットすることにしましょう。

従来のプログラミング言語でこのような処理を行うには、ループ構文を記述して繰り返し処理するというのが常套手段でした。 C 言語であれば、以下のようなプログラムを思い浮かべることができます。

const float x[] = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 };
float y[11];
for (int i = 0; i < 11; i++) {
    y[i] = x[i] * x[i];
}

関数プログラミングを提唱している言語ならば、同じ処理をするのに高階関数を適用することを思いつくでしょう。LISP の場合、写像処理を行う map を使って以下のように記述できます。

(map (lambda (x) (* x x)) ‘(-5 -4 -3 -2 -1 0 1 2 3 4 5))

かなりエレガントに書くことができました。LISP に限らず、高階関数という概念はものごとを抽象的にとらえる強力な武器になります。しかし抽象的な思考というものは、得てしてその道の入門者にとってはとっつきづらいものです。

そもそも、ここで実際に解決したいのは、xの数列に対応する x2 の値を求めるという単純な課題です。以下のような記述で、答えが求まらないものでしょうか。

x = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
y = x * x

x * x という表記は、x にひとつの数値を受け取ることを期待しています。これに対して、x に数値のリストを与えたとき、暗黙的に写像すなわちマッピングを行うようにすれば、ユーザは繰り返し処理を意識することなく結果を得られるようになります。

「暗黙的マッピング」の実装はこのような発想からスタートしました。

ケーススタディ

演算子に暗黙的マッピングを適用した例を以下に示します。

>>> [1, 2, 3, 4] + [5, 6, 7, 8]
[6, 8, 10, 12]
>>> [1, 2, 3, 4] + 5
[6, 7, 8, 9]
>>> ([1, 2, 3, 4] + [5, 6, 7, 8]) / 2
[3, 4, 5, 6]
>>> [3, 8, 0, 4] < [4, 5, 3, 1]
[true, false, true, false]

以下は、関数呼び出しに適用した例です。

>>> math.sqrt([1, 2, 3, 4])
[1, 1.41421, 1.73205, 2]
>>> string.upper(['aaa', 'bbb', 'ccc'])
['AAA', 'BBB', 'CCC']

「暗黙的マッピング処理」をさまざまなデータ入出力関数や処理関数と組み合わせると、制御構文を記述することなく多くの課題を解決することができます。以下に例をあげます。

>>> x = [1, 2, 3, 4]
>>> printf('result = %2d, %2d, %2d, %f\n', x, x * x, x * x * x, math.sqrt(x))
result =  1,  1,  1, 1.000000
result =  2,  4,  8, 1.414214
result =  3,  9, 27, 1.732051
result =  4, 16, 64, 2.000000

x * x や math.sqrt(x) などの式で「暗黙的マッピング処理」が働いてリスト要素ごとの演算をしています。さらに printf の実行でも、リストが引数として与えられたことによってやはりこの機能が作動し、要素ごとの表示処理をします。 printf は値を持たない関数なので、結果としてのリストは生成しません。

行番号をつけてファイルを表示するプログラムは以下のように書けます。

printf('%7d %s', (1..), readlines('hoge.txt'))

1.. と file#readlines() はリストではなくイテレータを返します。「暗黙的マッピング処理」はイテレータに対しても有効です。 1.. は 1 から始まる無限数列を表しますが、長さの異なるリストやイテレータが与えられた場合は短い方にあわせられるので表示する行数は file#readlines() が終了するまでになります。

以下は正規表現を使ってファイルから情報を抽出し、表示する例です。

import(re)
lines = readlines('hoge.h')
println(re.match(r'class (\w+)', lines).skipnil():*group(1))
file#readlines() で生成したイテレータ lines を受け取った re.match() は結果として match インスタンスを要素にするイテレータを返します。 re.match() は、パターンに合致しない場合は nil を返すので、イテレータのインスタンスメソッド skipnil() を使って nil 値をスキップするイテレータを生成します。 :* は後述するメンバマッピングオペレータで、上の例では、イテレータの各要素に対して match#group() メソッドを実行しています。