YAPC::Kyoto のタイムテーブルが発表された。 Raku(Perl6)初心者のNativeCallを使った全文検索ライブラリー作成日記 を聞きたいな〜と思ったら、 裏がマコピーの かんたん静的型付けPerlへの道のり だった。 マコピーのトークも気になる〜〜〜ってなって、とあるところでこうつぶやいた。
Rakuで型付けできるじゃんとか思いつつ「Perlをパースして型を抜き出す」これ面白そう、とかなる
でも、Raku(Perl 6)の型付けについてイマイチ理解が足りない。 さらに、Type Check=型チェックについて、どういう場合に、 どこで、どんなエラーを吐くかが分かってなかったのでインターネットアイドルさんの 力を借りつつ調べてみた。
追記
以下の点について、教えてもらってスッキリした。
- Typingと型チェックは別の概念
- 静的型付けや漸進的型付けであるのと、どういう風に型チェックするかどうかは別で考える
- Rakuは静的型付けで記述でき、後述するように型チェックの機能が備わっている
- ただし、エラーを吐くのがコンパイル時 or 実行時かは実装に関わる
my Int $i := 'foo'
でコンパイルエラーが出ないのは、単にまだ実装しきれてない or なんらかの深い理由があってのことだろう
RakuはGradual Typingな言語
まずRakuの型付けの性質について。 Rakuは、Javaのような静的型付けの側面も持ちつつ、動的型付けの利点がある Gradual Typing=漸進的型付けを持つ(だと思う)。漸進的型付けについては以下の記事がすごく面白いです。
RakuはGradual Typingだぜ!って言ってる人は他にもいる。
- Perl 6 gradual typing FTW!
- Perl 6 Types: Made for Humans | Zoffix Znet blogs.perl.org
- Calling subs and typing in Perl 6 | Opensource.com
型チェックの例
Perl 5は動的型付けな言語(ライブラリを使って漸進的型付けっぽくすることもできる)なので、
sub func
で定義したサブルーチンの引数にInt
を期待したいけど、
それを表現することはできなくて、以下のコードはそのままfoo
と印字する。
use v5.10;
sub func {
my $i = shift;
say $i;
}
func('foo');
まぁ当たり前なんだけど、これをRakuでやろうとするとシグネチャーを付けてこうなる。
use v6;
sub func( Int $i ) {
say $i;
}
func('foo');
実行しようとするとコンパイルエラーになる。
エラーメッセージが色付けされてていい感じ!
とはいえ、実はRakuにおける型チェックでコンパイルエラーが出るのは
このケース(X::TypeCheck::Argument
)だけのようだ。
X::TypeCheck::*
Rakuのコンパイルor実行時エラーの定義はRakudoの例外に関するソースを読むと結構分かる。
型チェックで例外を出すために実装されているのは、
X::TypeCheck
を継承したクラスX::TypeCheck::*
。
で、その中でも上の例で示したのはX::TypeCheck::Argument
で定義されている例外で、
これだけコンパイル時にエラーが出るみたい
(コンパイルエラーを出すのはX::Comp
ロールを実装しているものだと思うので、
ソースコードをみる限り、それを実装していないX::TypeCheck::Argument
でコンパイルエラーが出るのが謎)。
で、どういう場合に型チェックでどんなエラーが出るのかをいくか試してみた。 これらは
# 実行時エラーが出るコード
CATCH { default { put .^name, ': ', .Str } };
とすれば、例外の名前とメッセージをみることができる。
X::TypeCheck::Assignment
型宣言した変数に異なる型の値を代入しようとした時に発生する。
my Int $i = "foo";
CATCH { default { put .^name, ': ', .Str } };
# X::TypeCheck::Assignment: Type check failed in assignment to $i; expected Int but got Str ("foo")
X::TypeCheck::Return
サブルーチンの返り値が期待する型ではない時に発生する。
sub func( --> Int ) {
return 'foo';
}
func();
CATCH { default { put .^name, ': ', .Str } };
# X::TypeCheck::Return: Type check failed for return value; expected Int but got Str ("foo")
X::TypeCheck::Binding
指定した型以外の値をバインドしようとした時に発生する。
my Int $i := "foo";
CATCH { default { put .^name, ': ', .Str } };
# X::TypeCheck::Binding: Type check failed in binding; expected Int but got Str ("foo")
X::TypeCheck::Binding::Parameter
クラス内メソッドの引数が期待する型と異なる場合に発生する。
class A {
method func( Int $i) {
say $i;
}
}
A.new.func('foo');
CATCH { default { put .^name, ': ', .Str } };
# X::TypeCheck::Binding::Parameter: Type check failed in binding to parameter '$i'; expected Int but got Str ("foo")
X::TypeCheck::Assignment
クラス内のプロパティの値が期待する型と異なる場合に発生する。
class A {
has Int $.i;
}
A.new( i => 'foo' );
CATCH { default { put .^name, ': ', .Str } };
# X::TypeCheck::Assignment: Type check failed in assignment to $!i; expected Int but got Str ("foo")
X::TypeCheck::Argument
サブルーチンの引数が期待する型と異なる場合に発生する。
sub func( Int $i ) {
say $i;
}
func('foo');
CATCH { default { put .^name, ': ', .Str } };
で、これだけコンパイルエラーになる。
===SORRY!=== Error while compiling type.p6
Calling func(Str) will never work with declared signature (Int $i)
at type.p6:19
------> <BOL>⏏func('foo');
ちなみに、ロール周りのエラー
ロール周りでわりとコンパイル時にエラーを吐いてくれて、イケてる。
例えば、複数のロールを実装した場合にメソッドが被ってしまった時は以下の通り。
role A {
method func() {}
}
role B {
method func() {}
}
class C does A does B {}
stub
で定義したメソッドを実装しない場合。
role A {
method func() { !!! }
}
class C does A {}
まとめる
以上、漸進的型付けなRakuではコンパイル時もしくは実行時に型チェックが走り、
X::TypeCheck::*
で定義された例外を投げることが分かった。
他の言語の静的型付け、漸進的型付けがどうなっているのか、 Perl 5でどのように実現するのかが気になるところだが、 YAPC::Kyotoのマコピーのトークを聞けば分かるっぽい!! でもNativeCallでGroongaも面白そう!
わくわく〜〜〜。
追記
Twitterでつぶやいたら、
loading...
すごい勢いでエリザベスさんがレスしてくれた。
loading...
loading...