天然パーマです。

Raku(Perl6)を書く

YAPC::Kyotoのトークリストを見て、突然Rakuを書いてみたくなったのでいまさらながら書いてみた。

RakuとはPerl 6のこと。去年の10月にPerl 6からRakuへと改名された。 なぜ「いまさら」なのかというとPerl 6は20年くらい前に設計が始まり、 15年くらい前に動作可能な実装ができてたいからだ。 特に海外のPerlカンファレンスでは盛んにPerl 6の話がされていて、 2013年に行ったYAPC::NAでも 「Perl 6でWebフレームワーク作ったぜ!(遅いけどな)」みたいなトークがあった。 そして2015年のクリスマスにラリー・ウォールのもとリリースされた。 だから特別、目新しいものではない(とりわけ言語仕様)。 ただ、最近になってより実用性が高まってきたようだ。

ちなみにPerl 6はPerl 5とは互換性がなく全く別の言語と考えてよい。

以前Perl 6の話やそれで書かれたコードを見て 「ああ静的型付けができるんだな」とか「今までPerlになかったclassが使えるんだな」 とか感じることはあっても、いまいちピンと来ていなかった。 で、理解するには書くの一番早いということで、Rakuでいくつかのプログラムを書いてみた。

今回書いたのは言わずとしたらFizzBuzzといくつかのデザインパターンである。 デザインパターンは以下のQiitaの記事が分かりやすかったので、 そこに掲載されているJavaのコードをPerl 6らしく書きかえるという作業を行った。

さて実際書いたコードと共にRakuについて分かったことをまとめてみた。

環境づくり

コードを書く前にまずは環境づくり。 rakudo-starを入れるとPerl 6におけるcpan/cpanmコマンドのzefも使えるようになる。 Macの場合rakudo-starはhomebrewで簡単にインストールできた。

$ brew install rakudo-star

これでperl6コマンドが使える。

次にemacsにperl6-modeを入れた。Perl 5を書く時に使ってるcperl-modeだと 当然ながらPerl 6のコードを書く時に不便だった。 perl6-modeはMELPAからM-x package-install RET perl-6-modeすれば入る。

FizzBuzz

最初はFizzBuzzを書いてみる。色々な書き方があるが、Rakuっぽくかつ分かりやすいってことで以下のようなコードになった。 ちなみにこのブログで使ってるシンタックスハイライトは今のところPerl 6に対応していないので、 Perl 5のものを使っている。見づらいと思うが勘弁を。

for 1..100 -> $i {
    say to-fizz-buzz($i)
}

sub to-fizz-buzz (Int $n --> Str){
    my Str $s = '';
    given $n {
        when $n % 3 == 0 { $s ~= 'Fizz'; proceed }
        when $n % 5 == 0 { $s ~= 'Buzz'; proceed }
        when !$s.Bool { $s = ~$n }
    }
    return $s
}

これをRakuのスクリプトファイルを示す「.p6」拡張子を付けて、 fizz-buzz.p6という名前で保存する。

ここまで来てお気づきかもしれないが、Rakuでは変数名やサブルーチン名、 ファイル名などに関してスネークケース(fuzz_buzz)ではなくて、 ケバブケース(fuzz-buzz)を用いることが多いようだ。 これは最初、気持ちが悪かったが慣れた。

では、Rakuらしいところを見て行く。

1から100までを列挙するというのをforを使って書いている。

for 1..100 -> $i {
   say $i;
}

1から100までの配列を回し、それぞれの値を$iに代入している。 この辺からして、Perl 5の書き方とは違う。

サブルーチンの定義はこんな感じだ。

sub to-fizz-buzz (Int $n --> Str){}

Perl 6では動的な型付けと静的な型付けにどちらも対応している。 サブルーチン及びメソッドでは引数の型と戻り値の型を指定することもできる。

この例ではInt型の引数を受け取り、Str型を返すことを宣言している。

与えられた数値に対して、FizzなのかBuzzなのかFizzBuzzなのかそれとも そのままなのかを判定するところはgiven whenで書いた。

given $n {
    when $n % 3 == 0 { $s ~= 'Fizz'; proceed }
    when $n % 5 == 0 { $s ~= 'Buzz'; proceed }
    when !$s.Bool { $s = ~$n }
}

この時proceedと書いてるのは条件にマッチしたあとも次のwhenを評価したいから。 最後の$s.BoolStr型の$sに値が入っているかどうかを判断するために使っている。 この条件に当てはまれば、Int型の$nStr型に変換して代入している。

RakuでFizzBuzzを書くことに関しては八雲アナグラさんという沖縄の人が、 subsetmultiを使って書いてて面白い。 ここで言うto-fizz-buzzサブルーチンに渡ってくる型を判断して、 別々の処理を書くなんてこともできる。

Template Methodパターン

デザインパターンの中のTemplate Methodパターンを書いてみる。Javaの実装及び、プログラムの仕様は以下。

これもいくつか書き方があると思うが、Javaでいう抽象クラスをロールで表現してみた。

role Monser {
    has Str $.name;
    # Stubs
    method get-attack( --> Int ) { ... }
    method get-defence( --> Int ) { ... }
    method show-info {
        say '名前: ' ~ self.name;
        say '攻撃力: ' ~ self.get-attack;
        say '守備力: ' ~ self.get-defence;
    }
}

class Slime does Monser {
    method get-attack( --> Int )  {
        return 15;
    }
    method get-defence( --> Int ) {
        return 10;
    }
}

class Dragon does Monser {
    method get-attack( --> Int )  {
        return 60;
    }
    method get-defence( --> Int ) {
        return 45;
    }
}

my $slime = Slime.new( name => 'スライムくん' );
my $dragon = Dragon.new( name => 'ドラゴンさん' );

$slime.show-info;
$dragon.show-info;

ロールの話にいくまえにクラスの作り方。 classキーワードでクラス、roleキーワードでロールをそれぞれ定義できるのだが、プロパティの宣言、利用方法が結構面白い。

class A {
    has Str $.name; #アクセッサを自動でつくる
    has Int $!age; #プライベート
}

となっているがアクセッサをつくるとは、どういうことかというと$.nameで宣言すると、

my $a = A.new;
say $a.name;

とインスタンスでも使えるし、クラス内でもselfを参照して

method say-name {
    say self.name;
}

と呼び出すことができる。

さてロールの話。Monsterロールを実装するSlimeとDragonには get-attackget-defenceメソッドがなくちゃいけないっていうのが書ける。

role Monser {
    method get-attack( --> Int ) { ... }
    method get-defence( --> Int ) { ... }
    ...;

get-attackget-defencestubbed methodにすることで、 SlimeクラスとDragonクラスにオーバーライドすることを強要できる。 もし実装されてなければ、コンパイル時にエラーがでる。

これは、つまりJavaでいうInterfaceっぽいもの。 Perl 5の時はMoose::Rolerequiresでやってたが、Rakuではネイティブでこう書ける。

Iteratorパターン

次にIteratorパターン。これはArrayオブジェクトに備わっているiteratorを使った。

my $students = Array.new;
$students.append('Tanaka');
$students.append('Yamada');
$students.append('Suzuki');
$students.append('Sato');

my $iterator = $students.iterator;
loop {
    my $student := $iterator.pull-one;
    last if $student =:= IterationEnd;
    say $student;
}

$iterator.pull-oneでイテレーションの対象の一つを取ってきて、その型がIterationEndだったらループを抜ける。 $iteratorっていのはRakuにもともと備わっているIteratorロールを実装したものなんだけど、 こうしたAPIについては公式の「Perl 6 Documentation」で仕様や使い方を知ることができる。

Singletonパターン

最後にSingletoneパターン。

class Singleton {
    my Singleton $instance = Singleton.new;
    submethod BUILD {
        say "インスタンスを生成します。";
    }
    method get-instance {
        return $instance;
    }
}

my $obj1 = Singleton.get-instance;
my $obj2 = Singleton.get-instance;

if $obj1 === $obj2 {
    say '$obj1と$obj2は同じインスタンスです。';
} else {
    say '$obj1と$obj2は同じインスタンスではありません。';
}

出力結果は期待した通り、こうなる。

$ perl6 singleton.p6
インスタンスを生成します。
$obj1と$obj2は同じインスタンスです。

hasの代わりにmyで変数を宣言するとクラス属性になるので、それを利用してインスタンスを保持している。 BUILDは他の言語でいうコンストラクタにあたるので、 一度だけインスタンスが生成される時に呼ばれる。

他にも

  • Compositeパターン
  • Prototypeパターン

を書いてみた。さらに追加で書くかもしれない。以下のレポジトリにあります。

Rakuを書いてみて

書いてみてといっても数時間程度だけど、感じたことを列挙。

  • 実行環境つくるのがhomebrewで楽だった
    • Perl 5でOOPだ!といってMoose/Mouse/Mooを使ったプログラムを実行するにはモジュール入れなきゃいけないので、一から環境つくる人はそれに比べてRakuの方が早いと言える
  • コード初見だと見た目がキモくてわりと何やってるか分からない
    • 慣れればむしろ快適
  • コンパイル時に型チェックでエラー出してくれるのはPerl 5からしたら新鮮
  • 型、よい
  • Perl 5で頑張ってたのがスムーズに書けて嬉しい
  • とはいえ型宣言などをしなくても動くので、その辺はTMTOWTDI
    • 簡単なスクリプトだったらPerl 5の乗りで書けばいい
  • エラーメッセージが微妙に分かりにくいかもしれない
  • 一度だけなぞの挙動があった
  • 実行速度とかはまだよく分からない
  • 新しい言語を勉強するのにデザインパターンを書くのはいいかもしれない
  • 日本語のドキュメントは少ない

ということでRakuを書いてみたという話でした。

RakuにもCPANにあたるRaku Modulesというのがあるので、今度はそれを利用してみたい。 ゆくゆくはRakuのモジュールオーサーになるぞー!

今回お世話になった参考文献