天然パーマです。

RakuのSlurpyについて

引っかかったのでメモ。

Rakuでは関数やメソッドを引数を厳密に宣言させることができる、というかある程度しなきゃいけない。 例えば、それを利用して型や条件によってある一つの関数名でマルチディスパッチさせることができる。

  • 引数がStr型であるか
  • 引数がInt型であり、かつ10未満か
  • 引数がInt型であり、かつ10以上か

といったことを表現するコードは以下になる。

proto sub func($a) {
    say '--------------------';
    say $a;
    {*};
}

multi sub func(Str $s) {
    say 'func(Str $s) が呼ばれた';
}

multi sub func(Int $i where $i < 10) {
    say 'func(Int $i where $i < 10)が呼ばれた';
}

multi sub func(Int $i where $i >= 10) {
    say 'func(Int $i where $i >= 10)が呼ばれた';
}

func('hoge');

func(1);

func(50);

ちなみにproto subでそれぞれの関数を実行する前の処理を書いている。 実行結果は以下である。

--------------------
hoge
func(Str $s) が呼ばれた
--------------------
1
func(Int $i where $i < 10)が呼ばれた
--------------------
50
func(Int $i where $i >= 10)が呼ばれた

本題は引数をリストで受け取る時の方法。 この慣習はSlurpyと呼ばれている。 Sigilを「シジル」と言うように、これを日本語で表現してみたいけど、 「スラーピー」でいいのか、そもそも発音の仕方を知らないので、 とあえずSlurpyとしておく(誰か教えて…)。

Perl 5で配列の引数を受け取る場合は以下のように書く。

use v5.20;

sub func {
    my @args = @_;
    say join ' -> ', @args;
}

func(1,2,3);

でRakuでも同じのりで書くとどうなるか。

use v6;

sub func(@args) {
    say join ' -> ', @args;
}

func(1,2,3);

これを実行しようとするとこんなふうに怒られる。

===SORRY!=== Error while compiling args.p6
Calling func(Int, Int, Int) will never work with declared signature (@args)
at args.p6:7
------> <BOL>⏏func(1,2,3);

で、Slurpyの登場。@argsの受け取りを*@argsまたは+@argsと記述する。

sub func(*@args) {
    say @args.join(' -> ');
}

これはうまいことコンパイル、実行される。

じゃあ配列の中身の型をチェックしたい時はどうするか? sub func(Int *@args)とは書けないので、 以下のようにwhereで中身をチェックすればよい。

sub func(*@args where { $_.all ~~ Int}) {
    say @args.join(' -> ');
}

ちょっと冗長な気がするが、公式のドキュメントにこのやり方が書いてあった。

これを利用してマルチディスパッチさせるとこんなコードになる。

proto sub func(*@args) {
    say '--------------------';
    say @args.join(' -> ');
    {*};
}

multi sub func(*@args) {
     say 'func(*@args) が呼ばれた';
}

multi sub func(*@args where { $_.all ~~ Int}) {
    say 'func(*@args where { $_.all ~~ Int}) が呼ばれた';
}

multi sub func(*@args where { $_.all ~~ Str}) {
    say 'func(*@args where { $_.all ~~ Str}) が呼ばれた';
}

func('hoge', 'moge', 'hage');

func(1,2,3);

func(1,'hoge','moge');

結果。

--------------------
hoge -> moge -> hage
func(*@args where { $_.all ~~ Str}) が呼ばれた
--------------------
1 -> 2 -> 3
func(*@args where { $_.all ~~ Int}) が呼ばれた
--------------------
1 -> hoge -> moge
func(*@args) が呼ばれた

whereの条件にマッチした関数が、なければfunc(*@args)が呼ばれる。

なるほど!

ところでPerl 5のcallerみたいなのがはRakuにあるのだろうか…?

追記

callframeで取れる。skajiさんに教えてもらった。

use v6;

sub func {
    say callframe(0).code.name; #func
}
func();

ツッコミあれば、ついさっきDisqusを入れたのでそこでもなんでもいいのでください!以下参考になるページ。