この記事について
先日の稿で Perl 6 の解説を書こうと思っているなどと書いて言質を振り出してしまったので果たさねばならない。 Perl 6 の言語自体を解説するアップデートされた日本語資料は大変乏しいので、とりあえず Perl 6 Documentation を読んで紹介するだけでもいくらか価値があるだろうと思う。
率直に言って僕の英語の読解力は甚だ貧しいので、近頃話題になった「腐った翻訳」になるのを避けるためにドキュメントを読んでから試行した結果に基いて解説を書くことにする。 翻訳ではないので文章自体は元の文書とは対応しないが、とにかく Rakudo Perl 6 上での挙動としては正しい解説になるはずである。
読む順番は決めていないが、簡単そうな文書の中で面白そうなものから進めていきたい。
本文の前書き
近頃の言語は集合型が必要なことになっている。厳密にいえば数学的な集合というより、順序付けられておらず要素が重複しないコンテナがプログラムの構成部品として便利だということである。
Ruby も Python も Swift も、C++ の STL にだって (それが本当に集合と呼べるかは別にして) [Ss]et がある。
Sets, Bags, and Mixes は Perl 5 には標準で備わっていなかった集合の操作をサポートする Perl 6 のクラスとロール群について解説している。
Set
は単純な集合、Bag
は重複可能な集合 (つまり multi set) で、Mix
は要素に重みを持たせた集合を表す。 これらは一種の連想配列で、つまり要素に対して Set
はそれが自身の要素かという真偽値、Bag
はその要素をいくつ持っているかという整数値、そして Mix
はその要素に割り当てられた重みを実数値で対応させる。真偽値を 0/1 と対応させれば Set
の一般化が Bag
だし、さらにその一般化が Mix
とみなせる。
Bag
や Mix
を使えば重みに応じて確率的に要素をサンプリングするような処理も可能なため、各種のアルゴリズムやビジネスロジックの記述にも便利だろう。
Set
、Bag
そして Mix
はすべて不変クラスである。対応する可変クラスとして SetHash
、BagHash
そして MixHash
というクラスが存在する。
型システム上は不変クラスも可変クラスもそれぞれ Setty
、 Baggy
そして Mixy
(mixi ではない) というロールを適用しているため同じインタフェースを持つ。Mixy
は Baggy
を適用しているため Mix
は Bag
の真のスーパーセットである。Setty
だけ孤立しているのは多分 Bool
が数値型でないため実装の都合だろう。
可変クラスの不変クラスとのふるまい上の違いは、連想配列としてアクセスしたときに要素に再代入可能か (i.e., $set_hash<foo> = False
) と、要素を破壊的に非復元抽出するメソッドである grab
と grabpairs
が例外を投げないことの2点。後者はそもそも個別のクラスでなくロールにメソッドがあるのが変だと思う。
初期化
リストから型変換メソッドを使って生成するのが最も簡単である:
> <foo bar bar baz>.Set
set(foo, baz, bar)
> <foo bar bar baz>.Bag
bag(foo, baz, bar(2))
> <foo bar bar baz>.Mix
mix(foo, baz, bar(2))
Set
は要素の個数を覚えないが Bag
と Mix
は bar
が2回与えられたことが判別できる。
そういうわけで、Bag-of-Words は文字通りである:
> my $str = "Humpty Dumpty sat on a wall\nHumpty Dumpty had a great fall\n"
Humpty Dumpty sat on a wall
Humpty Dumpty had a great fall
> $str.words.Bag
bag(a(2), great, Humpty(2), had, wall, fall, Dumpty(2), sat, on)
ただしこの方法だと Mix
は整数値の重みしか取れないので Bag
と変わりない。実数の重みを与えるにはペアのリストに対して .Mix
メソッドを呼ぶ:
> (foo => 1.0, bar => 3.14, baz => 2.72).Mix
mix(foo, baz(2.72), bar(3.14))
ペアのリストを Set
や Bag
に変換することもできるが、その場合ペアの値はそれぞれ Bool
と Int
に型変換されて解釈される:
> (foo => True, bar => False, baz => True).Set
set(foo, baz)
> (foo => 2, bar => 1, baz => 0).Set
set(foo, bar)
> (foo => 0, bar => 2.72, baz => 3.14).Set
set(baz, bar)
> (foo => True, bar => False, baz => True).Bag
bag(foo, baz)
> (foo => 2, bar => 1, baz => 0).Bag
bag(foo(2), bar)
> (foo => 0, bar => 2.72, baz => 3.14).Bag
bag(baz(3), bar(2))
不変クラスの場合 &set
、&bag
そして &mix
というサブルーチンも提供されている:
> set <foo bar bar baz>
set(foo, baz, bar)
> bag <foo bar bar baz>
bag(foo, baz, bar(2))
> mix <foo bar bar baz>
mix(foo, baz, bar(2))
サブルーチンにペアのリストを渡すときは名前付き引数と解釈されないように記法に注意が必要である:
> mix(foo => 1.0, bar => 3.14, baz => 2.72)
Unexpected named parameter 'foo' passed
> mix: foo => 1.0, bar => 3.14, baz => 2.72
mix(foo, baz(2.72), bar(3.14))
要素の等値性
要素同士の等値性は ===
の意味で判定される。 つまり Str
や Num
のような値型はその内容で、Array
などの参照型は同じオブジェクトへの参照か否かで同値か判定される:
> set <foo bar bar baz 1 2 3 3 4>
set(4, foo, 1, baz, 3, bar, 2)
> (set ['foo'], ['foo'], { bar => 1 }, { bar => 1 })
set(bar => 1, bar => 1, foo, foo)
# REPL の出力 (.gist) だと構造が分かりにくいのでソースコードダンプ
> (set ['foo'], ['foo'], { bar => 1 }, { bar => 1 }).perl
set(["foo"],["foo"],{:bar(1)},{:bar(1)})
メソッド
先述のとおりどのクラスもよく似たインタフェースを持っている。 連想配列として使うためのメソッドと、集合からランダムに要素をサンプリングするメソッドが提供されている:
> my $b = bag <foo bar bar baz>
bag(foo, baz, bar(2))
> $b.kv
foo 1 baz 1 bar 2
# 存在しない要素の値は 0 になる (Set の場合は False)
> $b<foo>
1
> $b<bar>
2
> $b<quux>
0
# pick は非復元抽出
> $b.pick
bar
> $b.pick
baz
# 最大で要素数まで取れる
> $b.pick(*)
baz bar foo bar
> $b.pick(*)
bar foo baz bar
# roll は復元抽出
> $b.roll
bar
> $b.roll
foo
> $b.roll(10)
baz bar bar bar foo bar bar bar bar foo
> $b.roll(10)
baz baz foo bar bar bar bar baz bar foo
# 要素数を * にすると遅延無限リストが返ってくる
> $b.roll(*)
組み込み演算子
集合関連の操作は演算子として提供されている。ちょっと信じられないことに Perl 6 は組み込み演算子が ASCII 外の文字である場合がままあるが集合演算はその極致で、全部の演算が集合演算記号にマップされている。 幸い大部分には ASCII の同義語が定義されているので、あまり頻用する場合以外はそちらを使う方が良いと思う。基本的にスカラ同士の似た操作の演算子を ()
で囲ったものが集合演算子である(e.g., &infix:<(|)>
が集合の和、&infix:<(<)>
が部分集合のテスト、など。)
Perl 6 で高度な集合演算を記述する場合は LaTeX チートシートと SKK を用意しよう。
以下の演算子は集合の要素や部分集合の述語である:
&infix:<<∈>>
(&infix:<<(elem)>>
)&infix:<<∉>>
&infix:<<∋>>
(&infix:<<(cont)>>
)&infix:<<∌>>
&infix:<<⊂>>
(&infix:<<(<)>>
)&infix:<<⊄>>
&infix:<<⊆>>
(&infix:<<(<=)>>
)&infix:<<⊈>>
&infix:<<⊃>>
(&infix:<<(>)>>
)&infix:<<⊅>>
&infix:<<⊇>>
(&infix:<<(>=)>>
)&infix:<<⊉>>
以下の2つは ⊆
、⊇
の Baggy
版で、要素の有無に加えて対応する重みも比較される:
&infix:<<≼>>
(&infix:<<(<+)>>
)&infix:<<≽>>
(&infix:<<(>+)>>
)
以下の演算子はそれぞれ集合の和、積、差そして対称差である。なお対称差とは集合 A と B が与えられたときの (A \ B) ∪ (B A) のこと:
&infix:<<∪>>
(&infix:<<(|)>>
)&infix:<<∩>>
(&infix:<<(&)>>
)&infix:<<\>>
(&infix:<<(-)>>
)&infix:<<⊖>>
(&infix:<<(^)>>
)
以下の2つ (判読困難だがそれぞれ U+228D と U+228E) は ∩
、 ∪
の Baggy
版で、重みの積と和をそれぞれ取る:
&infix:<<⊍>>
(&infix:<<(.)>>
)&infix:<<⊎>>
(&infix:<<(+)>>
)
まとめ
Perl 6 の集合関連のクラスとロールについて説明した。
Perl 5 では集合計算や数の集計、重みに応じた要素の乱択などあらゆる操作にハッシュが広く用いられていた。ハッシュは単純な非順序付け連想配列であり必ずしも理想的な実装ではなかった。 用途に応じて Set::Object
などの CPAN モジュールも利用されたが、新しい演算子を定義できないとか tie
が低速であるといった Perl 5 の言語及び実装上の制約により、組み込み型であるハッシュほど利便性が高くなかった。
Perl 6 は用途に応じて選択できる複数のコンテナを提供し、操作は新しい演算子の導入によって簡潔に記述できる (やりすぎの感があるが。)
閑話
ところで Set
、Bag
および Mix
は組み込み型だが Perl 6 で実装されている。これは組み込み型のインタフェースをユーザ定義型でも同様に実装できることを示唆している。 Perl 6 においては配列やハッシュもコンテナクラスの1つに過ぎず、そのインタフェースは単なるメソッドや演算子である。 組み込みデータ型が明確に区別され、同様のインタフェースで操作するには tie
という追加操作を必要とした Perl 5 とはこの点でも大きく異なる。
コメント
コメントを投稿