ゆーすけべー日記

和田裕介のブログです。

Raku(Perl6)を書く

Posted at — Jan 17, 2020

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すれば入る。

perl6-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は他の言語でいうコンストラクタにあたるので、 一度だけインスタンスが生成される時に呼ばれる。

他にも

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

Rakuを書いてみて

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

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

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

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

comments powered by Disqus