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

メンバマッピング

複数のインスタンスがあって、それらに対して同じメソッドを実行したり、メンバ変数を参照したい場合があります。これを可能にするのがメンバマッピングです。

メンバマッピングがどのように働くのかを見るため、簡単な構造体 Fruit を作ります。

Fruit = struct(name:string, price:number) {
    Print() = printf('name:%s  price:%d\n', self.name, self.price)
}

関数 struct は、引数で指定したメンバ変数とブロック式の中で定義したメンバ関数を持つ構造体を作り、そのコンストラクタ関数を返します。このコンストラクタ関数を:

fruit = Fruit('apple', 100)

のように呼び出すとインスタンスをひとつ生成します。以下はインスタンスに対するメンバアクセスの例です。

printf('%s costs %d\n', fruit.name, fruit.price)
fruit.Print()

それでは、構造体 Fruit のインスタンスのリストを作ってみましょう。

fruits = [
    Fruit('apple', 100), Fruit('orange', 80), Fruit('grape', 120)
]

このリスト中のインスタンスに対して、メソッド Print を実行するにはどうすればよいでしょうか。はじめに思いつくのは、繰り返し構文を使ってインスタンスをひとつずつとりだし、メソッドを実行する方法です。これは Gura の関数 for を使って以下のように書けます。

for (fruit in fruits) {
    fruit.Print()
}

メンバマッピングを使うと、上の処理は以下のように記述できます。

fruits::Print()

オペレータ :: がメンバマッピングの処理を表していて、この場合、リスト fruits の要素についてそれぞれメソッド Print を実行しています。

この程度の表記の簡略化なら、あまりメリットを感じないかもしれません。では次の課題です。 fruits の値段の合計を求めてみましょう。

繰り返しを使うと以下のように書けそうです。

priceSum = 0
for (fruit in fruits) {
    priceSum += fruit.price
}

メンバマッピングを使うと以下のようになります。

fruits::price.sum()

ずいぶんと簡単に書くことができました。fruits::price を評価すると各インスタンスのメンバ変数 price の値を評価し、それらをまとめたリストを返します。そのリストに対してメソッド sum を実行し合計を求めています。

次の課題はもう少し複雑です。名前のうち一番長いものは何文字でしょうか。

繰り返しを使った解決法は以下のようになります。

lenMax = -1
for (fruit in fruits) {
    len = fruit.name.len()
    if (lenMax > len) {
        lenMax = len
    }
}

メンバマッピングを使うと、これが以下のように書けます。

fruits::name::len().max()

fruits::name で名前のリストを得て、それに対してさらにメソッド len を実行して 名前の長さ のリストにします。そしてそのリスト全体に対してメソッド max を評価することで最長の長さを得られることになります。

このような処理が簡単な記述で実現できることは、他の処理と組み合わせていく上で大変有利です。上の処理を使って、一番長い名前の長さにそろえて表示する例は以下のようになります。

printf('%-*s %d\n', fruits::name::len().max(), fruits::name, fruits::price)

ところで、メンバマッピングオペレータ :: は逐次リストを生成しますので、 name や len() の処理結果もそのたびにリストを作ることになります。これですと fruits の数が多くなると作るリストの要素数も増えてしまい、速度効率やメモリ効率が悪くなります。

これを解決するため、メンバマッピングオペレータ :* があります。このオペレータを使うと、その評価結果はイテレータになり、実際の演算が必要なときまで評価が先送りされます。上の例は以下のように記述することができます。

printf('%-*s %d\n', fruits:*name:*len().max(), fruits:*name, fruits:*price)

メンバマッピングを使った少し長めのプログラムを見てみましょう。以下は、名前や年齢を記録した CSV ファイルを読み込んで簡単な統計処理をする例です。データ列を扱うための繰り返し処理がなくなっている点にご注目ください。

import(csv)

Person = struct(name:string, email:string, gender:string, age:number, rest*)
people = Person * csv.reader(open('50records-en.csv'))

familyNames = (people:*name:*split(' '):list)::get(0).sort()
familyNameSet = set(familyNames)

println('== occurance counting ==')
Stat = struct(name:string, occurance:number):map
stats = Stat(familyNameSet, familyNames.count(familyNameSet):map)
printf('%s(%d), ', stats:*name, stats:*occurance)
println()
stats = stats.sort(&{-($stat1.occurance <=> $stat2.occurance)}):stable
printf('%s(%d), ', stats:*name, stats:*occurance)
println()

println('== some statistical information ==')
genders = people::gender
printf('all=%d male=%d female=%d\n', people.len(), genders.count('male'), genders.count('female'))
ages = people::age
printf('age < 30        %d\n', ages.count(&{$age < 30}))
printf('30 <= age < 40  %d\n', ages.count(&{30 <= $age && $age < 40}))
printf('40 <= age < 50  %d\n', ages.count(&{40 <= $age && $age < 50}))
printf('50 <= age < 60  %d\n', ages.count(&{50 <= $age && $age < 60}))
printf('60 <= age < 70  %d\n', ages.count(&{60 <= $age && $age < 70}))
printf('70 <= age       %d\n', ages.count(&{70 <= $age}))