Junction

この記事は、Perl6 Advent Calendar 2011の17日目です。


さて、今日はPerl6の持ち味の一つであるJunctionの紹介をしようと思います。

Introduction

少し唐突ですが、「変数iが1か2なら〜」という条件分岐、皆さんはどう書くでしょうか?たとえばPerlで素直にif文を使うと以下のようになると思います。

if ($i == 1 || $i == 2) {
   ...
}

「$i ==」という部分が二回も出てきて美しくない、そう思いませんか。Perl6には、より簡潔で解りやすくコードを書くために、Junctionという魔法が用意されており、以下のようなコードで同じ結果が得られます。

if ($i == 1|2) {
   ...
}

「iが1か2なら」という人語をそのままコードに落とし込んだ感じです。これは便利ですね。一体何が裏側で行われているのでしょうか。

Junctionとは

そもそもJunctionとは何なのでしょう?実例を挙げるならば、上のコードの「1|2」がJunctionの一つです。Junctionは複数のデータを持つ事が出来るオブジェクトで、Junctionに対する操作はそれぞれの要素に適用されます…と言っても何言ってるのか解らないので、実際に使ってみましょう。

my $jct = 1|2|3;
say $jct ** 2;

#> any(1, 4, 9)

「any(...)」という見慣れないものが出てきましたが、これがJunctionの正体です。Perl6ではinfix:<|>は論理和ではなくany Junctionを生成する演算子に変わりました。

まず$jctには「1か2か3である」という情報が保持されており、それを二乗すると、それぞれの要素が二乗されて「1か4か9である」という状態になります。もう少し別の例として、関数呼び出しにJunctionを渡してみましょう。

sqrt(1|4|9);
#> any(1,2,3)

このように、関数に対してJunctionを渡すと、それぞれの要素に関数が適用され、その結果が同じJunctionとして帰ってくるわけです。ここで特筆すべき点は、関数側はJunctionを受け取る事を前提とした処理を書く必要が無い点です。引数にJunctionを渡すと、自動的に各要素に対して並列に呼び出しが行われます(Junctive Autothreading)。つまり、(処理系が対応していれば)面倒な記述一切無しで並列処理が出来る!すばらしいですね。逆に言うと、要素が前から順に実行されていくとは限らないので注意が必要です。
ちなみにmultiple dispatchを使ってJunctionを受け取った場合の処理を記述する事も出来ます。

Junctionの種類

ここまでの例ではanyしか扱っていませんでしたが、Junctionには他にも種類があります。基本的な性質は同じなのですが、ブールコンテキストで評価した時に返す値が異なります。

any

要素をそれぞれブールコンテキストで評価し、一つでも真のものがあれば真を返す(OR)。infix:<|>で生成。any(...)という書き方も出来る。

all

要素をそれぞれブールコンテキストで評価し、全てが真であれば真を返す(AND)。infix:<&>で生成。all(...)という書き方も出来る。

one

要素をそれぞれブールコンテキストで評価し、ただ一つだけが真であれば真を返す(XOR)。infix:<^>で生成。one(...)という書き方も出来る。

none

要素をそれぞれブールコンテキストで評価し、全てが偽であれば真を返す(NOR)。infixは無く、none(...)で生成する。


となっています。ifの例では、仮に$iが1なら条件式がany(True, False)を返すので、ブールコンテキストで評価するとTrueになるためブロック内が実行されるわけですね。

まとめ

今まで有りそうで無かった機能、Junctionを説明してきました。簡単な例ではピンと来ないかもしれませんが、実際にコードを書いてみると便利さに驚きます。もう少し応用的な使い方は去年の本家の記事(@uasiさんによる翻訳版)に紹介されていますので、是非参考にしてみてください。今まで単純なくせに面倒だった処理が、実にカッコ良く記述できます。