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になるためブロック内が実行されるわけですね。
関数を作る
この記事はPerl6 Advent Calendar 2011の16日目です。
さて、いわゆる構造化プログラミングでは外せない関数ですが、Perl6では幾つか書き方がありますので紹介しましょう。
sub
Perl5でもおなじみ、subキーワードを用いた関数定義の方法です。Perl5とは違う点として、シグネチャを記述できるようになった事が挙げられます。これはマルチディスパッチと組み合わせる事でとても効果を発揮するのですが、これについては以前の記事を参照してください。
# 普通の関数定義 sub func ($arg) { ... } # 名前を省略すると無名関数に sub ($arg) { ... } # マルチディスパッチする場合はmultiキーワードを付ける multi sub (Int $arg) { ... } multi sub (Str $arg) { ... }
pointy block
pointy blockという構文を使う事で、より少ない記述で関数(厳密にはブロック)が定義できます。矢印(->)に続けて引数の定義を書き、そのあとに中括弧で処理するブロックを置きます。
# pointy block -> $arg { $arg.sqrt } # 上と大体同じ { $_.sqrt } # 更に省略 { .sqrt }
placeholder variable
11/12/18追記
ブロックの中にハット(^)から始まる変数を宣言する事で、渡した引数が出現した順変数名の辞書順に格納されていきます。
{ $^a - $^b }(1,2) #> -1
WhateverCode
placeholderと似ていますが、変数ではなくアスタリスク(*)を置く事で、渡した引数がアスタリスクと置き換えられます。
(*.sqrt).WHAT #> WhateverCode() (*.sqrt).(4) #> 2
WhateverCodeは中括弧が必要無いため、mapなどに渡すブロックを作る時に役立ちます。
(1,2,3,4,5).map(*.sin); #> 0.841470984807897 0.909297426825682 0.141120008059867 -0.756802495307928 -0.958924274663138 # 普通に書くとこんな感じ? (1,2,3,4,5).map({$_.sin});
まとめ
詳しく仕様を見ていくと色々違いはあるのですが、Perl6にはこのように様々な関数定義の構文が用意されています。
数列演算子
この記事は、Perl6 Advent Calendar 2011の11日目です。
今日はオブジェクトの話から少し離れて、数列演算子を紹介しましょう。
infix:<...>は、数列を生成する演算子です。単純に数字をおいた場合、第一オペランドから第二オペランドまで1ずつ変化する数列を返します。
> 1...4 1 2 3 4 > 4...1 4 3 2 1
第一オペランドに要素数2のリストを指定する事で、その2数の差を公差とする等差数列が得られます。
> 2,4...10 2 4 6 8 10 > 5,3...-7 5 3 1 -1 -3 -5 -7
この時注意すべき事は、この演算子は第二オペランドが出現するまで計算を繰り返す点です。次の例を見てください。
> 2,4...9 #(無限ループ)
この場合、2,4,6,8,10…と計算を続けていきますが、永遠に9は出てこないので無限ループに突入します。
第一オペランドに要素数3のリストを指定する事で、その3数の比を公比とする等比数列が得られます。
> 2,4,8...64 2 4 8 16 32 64
この場合も等差数列と同様に、第二オペランドが終端できない場合、無限ループに陥ります。
無限リスト
この演算子の面白い点は、第二オペランドにアスタリスク(*)を指定する事で、無限リストを生成できる点です。
> 3...* ... > 3,2...* ... > (3,2...*).[^10] 3 2 1 0 -1 -2 -3 -4 -5 -6
変数に代入する事もできます。
> my @a = 3,2...* ... > @a[^10] 3 2 1 0 -1 -2 -3 -4 -5 -6
まとめ
数列演算子を使う事で、単純な数列であれば簡単に作る事ができます。また、(この記事では割愛しますが)gather-takeを使って遅延評価リストを使う事もできます。
メタメソッド
この記事はPerl6 Advent Calendar 2011の10日目です。
内容的には前回の「クラスとオブジェクト」の続きのような感じなので、この記事を読まれる前に、そちらを先に読む事をお勧めします。
さて、前回の記事ではPerl6のオブジェクトがメタクラスによって管理されている事を紹介しました。今回は、そのメタクラスのメソッド(メタメソッド)にどんなものがあるのかを紹介して行こうと思います。実用的なプログラミングだけでなく、デバッグや内部構造の勉強に非常に便利なので、ガンガン使っていきましょう!
methods
methodsメタメソッドは、そのオブジェクトが持っているメソッド(オブジェクト)の一覧を返します。オプションにlocalを指定する事で、そのクラスで定義されている(親クラスから受け継いだものではない)メソッドだけを表示する事ができます。このメタメソッドを使う事で組み込みのクラスのメソッドを簡単に調べられるので、「xxしたいんだけど、それっぽいメソッドないかなー」というときにとても便利です。
attributes
methodsのattributes版です。
find_method
引数に指定した名前のメソッドオブジェクトを返します(存在しない場合はMuが返る)。特定のメソッドのシグネチャや返り値を見る時に便利ですね。
add_method
クラスまたはオブジェクトに対して、メソッドを登録します。クラスに対してメソッドを追加した場合は、add_methodを呼ぶ前に生成されたインスタンスであっても、そのクラスのインスタンスは全てそのメソッドを持ちます。オブジェクトに対してメソッドを追加した場合は、Rubyの特異メソッドのように、そのオブジェクトのみに対してメソッドが追加されます。
これは私の思い違いでした。クラスに対して追加した場合も、オブジェクトに対して追加した場合も同じ挙動になるようです。検証が甘くて申し訳ありません…。
以下にadd_methodを使ってメソッドを追加する例を示します。
クラスに対してメソッドを追加する例
class C { } my $c1 = C.new(); my $c2 = C.new(); C.^add_method("do_it", method () { say "Done!"}); $c1.do_it; #> Done! $c2.di_it; #> Done!
オブジェクトに対してメソッドを追加する例
class C { } my $c1 = C.new(); my $c2 = C.new(); $c1.^add_method("do_it", method () { say "Done!"}); $c1.do_it; #> Done! $c2.do_it; #> Done!
ちなみにこのadd_method、メソッドを追加するにあたって色々なオプションがあります。詳しくはsrc/Perl6/metamodel/BOOTSTRAP.pm を参照してください。
(追記)
あるオブジェクトだけに特有のメソッドを持たせるには無名roleを使うしかないようです。
class C { } my $c1 = C.new; my $c2 = C.new; $c1 does role { method do_it { say "Done!" } } $c1.do_it; $c2.do_it; #>Done! #>Method 'do_it' not found for invocant of class 'C'
ちなみに、無名roleを追加したオブジェクトの型を見ると、
C+{<anon>}()
という型が新たに作られているのが判ります。これについては誰かの別の記事で説明があるかもしれません(この記事では割愛します)。
add_attribute
add_methodのattribute版です。
parents
そのオブジェクトの親クラスの一覧を返します。引数にtreeオプションを渡す事で、フラットなリストではなく継承関係のツリーが得られます。
まとめ
他にもメタクラスが持つメソッドは幾つもありますが、普段使う上で便利なのはこのくらいでしょうか。こうやって手軽にオブジェクトの中身を見たり、書き換えたりできる所はPerl6の魅力の一つでもあります。
クラスとオブジェクト
この記事はPerl6 Advent Calendar 2011の6日目です。
前々回のuasiさんの記事が中々濃い内容だったので、補足としてPerl6のオブジェクトモデルについて簡単に解説しようと思います。
前提
Perl6の世界は(一部のnon-objectなプリミティブ型を除いて)オブジェクトで構成されています。全てのオブジェクトはクラスに属し、全てのクラスはルートクラスであるMuクラスを(そして一般的なクラスはMuを継承したAnyクラスを)継承しています。
オブジェクトに関する基本的な操作はMuクラスとAnyクラスに用意されていて、ユーザが独自に定義したクラスも暗黙的にAnyクラスを継承したものと見なされます。
クラスの定義
クラスを定義するにはclassキーワードを使います。
class C { ... }
クラスにはattributeとmethodを持たせる事が出来ます。
attribute
attributeの定義にはhasキーワードを使い、myを使って変数を宣言するのと同じように記述しますが、シジル(変数名の先頭に付く$,@,%)に続けてツイジルという記号を指定する事で、その属性へのアクセス制御を行います。
has $!attr のようにツイジルにエクスクラメーションを指定すると、その属性へのアクセサメソッドは自動的に生成されず、クラスの外からは読む事も書くこともできません。
has $.attr のようにツイジルにピリオドを指定すると、その属性への読み取りメソッドが自動的に生成され、$obj.attr のような記述で外部から値を読み込む事が出来ます。
has $.attr is rw のように、ツイジルにピリオドを指定した上で"is rw"トレイトを指定する事で、上記の読み取りメソッドに加えて書き込みメソッドが追加され、$obj.attr = 1 のように代入式の左辺値に置く事が出来るようになります。
class C { has $!internal; #内部からのみアクセス可能 has $.readonly; #外部からは読み取りのみ可能 has $.writable is rw; #外部からも書き込み可能 }
method
methodの定義にはmethodキーワードを使い、subを使って関数を宣言するのと同じように記述します。オブジェクトに対するメソッド呼び出しはピリオドを使い、$obj.method() のように記述します。
class C {
method do_it (Int $i) { ... }
}
コンストラクタ
Perl6では(一般的には)コンストラクタにnewメソッドを使います。クラス定義にnewメソッドを含めなかった場合は、親クラスから継承されたnewメソッドが利用できます(前述の通り、全てのクラスはMuクラスを継承しており、Muクラスには汎用のnewメソッドが用意されています)。
自分でnewメソッドを書く場合には、メソッドの最後にblessメソッドの返り値をそのまま返るようにします。一番単純な書き方ではblessに{属性名 => "値"}のハッシュを渡しますが、ハッシュの代わりにself.CREATEメソッドを返す事で、名前付き引数を使った初期化が可能になります(この説明は非常に乱暴かつ本質的でないのでS12-objects "Construction and Initialization"を参照してください)。
class C { has $!attr; method new ($i) { # 一番簡単? self.bless({ attr => $i }) } method new ($i) { # CREATEを使う版。やろうと思えばCREATEの引数に色々指定できる self.bless(self.CREATE(), attr => $i); } method new ($i) { # 上と等価、スマートっぽい self.bless(*, attr => $i); } }
methodもmultiを指定する事でmultiple dispatch(過去記事参照)が可能なので、newメソッドを複数用意してあげるとイケてる感じのクラスが書けるかもしれませんね。
メタクラス
さて、Perl6ではオブジェクトの振る舞いを定義したり、オブジェクトの管理のためにメタクラスという概念を持っています。オブジェクトが保持するデータではなく、オブジェクトそのものの情報を得たり、オブジェクトの構造を改変するにはメタクラスにアクセスする必要があります。逆に言えば、メタクラスを利用することによって、あんなことやこんなことが出来るようになるわけですね。
あるオブジェクトに対して、それを管理しているメタクラスを得るにはHOWメソッドを使います。試しにmethodsメタメソッドを使ってオブジェクトに登録されているメソッドの一覧を取得してみましょう。
my $i = 1; say $i.HOW.methods("i"); #> Int Num Rat abs Bridge chr succ pred sqrt (その他一杯、省略
ここでmethodsにオブジェクトの名前を渡しているのは、$iを管理しているメタクラスは他のオブジェクトも管理しているため、メソッドの一覧を取得する対象のオブジェクトを特定する必要があるためです。これは以下のように省略表記する事が出来ます。この場合、オブジェクト名は必要ありません。
my $i = 1; say $i.^methods;
この他にも、オブジェクトに対してメソッドや属性を追加するadd_methodやadd_attribute、メソッドオブジェクトを取得するfind_method、継承関係を返すparentsなど、黒魔術だけでなくデバッグにも便利なメタメソッドがたくさん用意されています。これらのメタメソッドはsrc/perl6/metamodel以下に定義されていますので、気が向いたら覗いて見ると良いでしょう。
今挙げたうちの一つadd_methodについてですが、実はオブジェクトを生成する際、メソッドの追加にはadd_methodが内部的に呼ばれています。前回のuasiさんの記事では「メタクラスを差し替えてadd_methodの挙動を独自のものに置き換える事で、追加するメソッド名に手を加えている」というワザが使われているのでした。
基本的な制御構文の紹介
この記事はPerl6 Advent Calendar 2011の3日目です。
今年は誰も名乗りを上げないようなので、私が勝手にPerl6 Advent Calendarを立ててしまいました。まだまだ参加者募集中なので、我こそは!という方は是非ATNDに登録をお願いします。
前回はぜっぱち(@Zeppachi)さんによるPerl6の紹介でしたので、私もそれに続こうと思います。この記事のテーマは「制御構文」ということで、Perl6における一般的な制御構文を、Perl5と比較しながら紹介して行きましょう。
また、この記事の内容は締切がやばい解りやすさを優先したい等の理由から、あまり厳密でない説明が多分に含まれます。詳しく知りたい方は、一日目の記事でも紹介されていましたがPerl6の仕様であるSynopsisのBlocks and Statementsを参照してください。
if/unless
Perl6の条件分岐はPerl5のそれと殆ど変わりません。ifの直後に来る条件節を評価して、真であれば続くブロックを実行します。elseやelsifについても同じです。違う点を上げるとすれば、条件節を囲む丸括弧が必要無くなったくらいでしょうか。
注意すべき点としては、ブロックを始める中括弧を条件節に隣接して書かない点でしょうか。他の構文にもいえる事ですが、中括弧の直前はスペースを空けないと、ハッシュへのアクセスだと解釈されてしまいます。
if $cond_1 { # $cond_1 => True } elsif $cond_2 { # $cond_1 => False && $cond_2 => True } else { # それ以外 }
loop
C言語スタイル(3-part spec)のfor文を書くための構文です。Perl5ではfor/foreachを使っていましたが、Perl6ではfor文の機能が拡張されるに従い、loop分に分離される事になりました。条件節の丸括弧は省略できません。
loop ($i = 0; $i =< 10; $i++ ) { # $iを0から10までインクリメントしながらブロックを実行 } loop (;;) { # 無限ループ }
while/until
条件節が真/偽である間、後ろに続くブロックを繰り返し実行します。Perl5ではwhile/untilを後置する際にdo文を使っていましたが、Perl6ではrepeatに変更されました。これは、Perl6のコードをより英語的な言い回しに近づけるための配慮(?)だそうです。条件節の丸括弧は省略できます。
while $i < 0 { # $iが0未満の際に処理を継続 } repeat { # 後置する場合はrepeatを使用 } while $i < 0; repeat while $i < 0 { # 後置whileと等価なので注意 }
for
for文はデータの集合に対してイテレートを行う文法です。先にも述べた通り、Perl6でのfor文はかなり強力な構文になっています。forに続けて配列やリストなどを記述すると、各要素が先頭から順に$_にコピーされ、ブロックの内容が実行され、要素が無くなると繰り返しを終了します。
for @list { # @listの要素が先頭から$_にコピーされて実行される } for 1,2,3 { # リストを直接書いたりも出来る } for %hash { # forにハッシュを渡すと、keyとvalueのPairが$_にコピーされる }
また、pointy blockという記法を使う事で、$_ではなく明示的に名前を付けた変数に値をコピーできます。矢印の右側に複数の変数を記述する事で、その個数ずつ取り出す事も出来ます。この時、ループの最後で取り出す個数が足りないとエラーになるので注意しましょう。このpointy blockは関数の定義の類なので、関数のシグネチャに書ける値の制約やデフォルト値なども記述できます。
for @list -> $element { # $elementに要素が順にコピーされる } for @list -> $element is rw { # 配列の要素を直接変更したい場合は"is rw"を指定する } for @list <-> $element { # 上と等価 } for @list -> $a, $b { # 先頭から2つずつ要素を取り出すことも可能 } for 1,2,3, -> $a, $b { # リストが奇数個なので失敗する } for 1,2,3 -> $a, $b? { # 後ろの変数に省略可能(optional)を指定すると、要素が足りなかったときは$bにMuが入る }
ハッシュにはkeyとvalueを交互に返すkvメソッドが用意されており、for文と組み合わせる事で簡単に処理できます。
for %hash.kv -> $key, $value { # keyとvalueがそれぞれ$keyと$valueにコピーされる }
また、Zオペレータと組み合わせる事で、複数の配列を先頭から舐めて処理するような事も出来ます。
for @a Z @b -> $elem_a, $elem_b { # $elem_aには@aの要素が、$elem_bには@bの要素が順にコピーされる }
またRangeを与える事で、ループを特定の回数繰り返すような使い方もできます。
for 0..10 { # 0から10まで$_にコピーしながら11回繰り返す } for 0..^10 { # 0から(10-1)=9まで$_にコピーしながら10回繰り返す } for ^10 { # 上の省略表記 }
もちろん後置する事も出来ます。
{ ... } for @list; .say for @list; # 後置の場合は、処理が1ステートメントなら中括弧も省略可能
まとめ
これでPerl6の基本的な制御構文は一通り紹介し終えました。じつは他にも載せきれなかった構文が幾つもあるのですが、もしかするとこれからの記事で出てくるかもしれませんね。もちろん今日紹介した構文は何度も出てくると思いますので、これからの記事を読む助けになれば幸いです。
対話環境perlshを使ってみる
Perlにもirbみたいな対話環境が欲しいなーと思ったのでググったら、perlshなるツールが良いようなので導入してみました。中々悪くなかったので、perlbrew + cpanm での導入メモを書いておきます。
perlshの導入
$cpanm perlsh
と実行すると、perlshという名前のモジュールは無いと教えてくれるんですが、そのときTerm-ReadLine-Gnuというツール群が自動的に入ります。その中にperlshがあるので、適当にpathの通った場所に移動すれば使えるようになります。
cp ~/.cpanm/latest-build/Term-ReadLine-Gnu-1.20/eg/perlsh ~/perl5/perlbrew/perls/perl-5.14.0/bin/perlsh
場所は各自の環境に合わせて適宜アレンジしてください。後は起動するだけで(ある程度は)快適な対話環境が使えるようになります。
Perlbrew使ってる環境だと、shebangは#!/usr/bin/env perlにしておくのが無難かもしれません。
perlshの設定
~/.perlshrcに色々書くことでperlshを改造できます。
詳しくはこちら。
http://jijixi.azito.com/cgi-bin/diary/index.rb?date=20080328
$PerlSh::STRICTに1を入れることでuse strict;っぽい動作になるみたいなんですが、手元のperlshだとmyを使った変数宣言が上手くいきません。evalの中でレキシカルスコープが終わってしまうので、次の入力行のスコープでは見えなくなってしまうのが原因なのかなーと思っているんですが、この辺り理解が浅いので確証は持てません…。Devel::LexAliasなる悪魔的モジュールはスコープの外に変数を輸出できるようなのですが、evalの中でlexaliasを呼ぶとSegmentation faultでperlが落ちてしまいます。XSが何か悪さをしているんでしょうか…。
Perlはどれだけ勉強しても解らないことばかりで怖いですね。