Optionally Typed Ruby
先日GoogleからDartという言語が公開されて、なにかRubyに取り込んで遊べる面白い機能ないかなと仕様見てみたんですが、なんだか全部どこかで見たような機能ばっかりで非常にがっかり。こんなんじゃネタにならないよ、と愚痴ってたら@makoto_inoueさんから要望がありました。
ご存じない方のために軽く説明しておくと、Dartはこんな感じの言語です。
class Greeter implements Comparable { String prefix = 'Hello,'; Greeter() {} Greeter.withPrefix(this.prefix); void greet(String name) { print('$prefix $name'); } int compareTo(Greeter other) { return prefix.compareTo(other.prefix); } }
変数に型があったり、メソッド定義に返り値や仮引数の型が必要だったり、いろいろ型苦しい感じに見えます。が、実はそれは間違いです。というか罠です。
例えば次のように宣言されているcompareToメソッドですが
int compareTo(Greeter other)
返り値はintじゃなくて構いませんし、引数もGreeterじゃなくて構いません。上記のような宣言で、実体は数値を受け取って真偽値を返すメソッドを定義したとしても、普通にコンパイルしただけでは警告すら出ません(たぶん)*1。つまりDartにおいて型はただのアノテーションで、実行にはなんの影響も与えないと言うことです。
恥ずかしながらソースコードの見た目にとらわれて@makoto_inoueさんに言われるまで見逃していましたが、確かに
型は付けられるけど無視される
というのはとても新しいです。この仕様一つでDartを変態言語と断言できるレベル。
そして、変態仕様はぜひとも我らがRubyにも取り込みたい。ということでやってみました。Optionally Typed Ruby。こんな感じ。
def method(arg) arg end def typed_method(arg) : String arg end def fully_typed_method(arg : String) : String arg end var = 'var' puts var var_with_type : String = 'var_with_type' puts var_with_type var_from_method : String = typed_method('var_from_method') puts var_from_method @ivar : String = fully_typed_method('@ivar') puts @ivar
実行結果。
$ ./truby -Ilib -I. truby_test.rb var var_with_type var_from_method @ivar
型付はAS3やGo言語みたく「変数 : 型」にしました。JavaやDartの形式「型 変数」だとメソッド呼び出しと区別がつかないもので・・・。
文法
型付き変数宣言
変数名 : 型 = 値
型付きメソッド宣言
def メソッド名(引数 : 型, ...) : 型
なお本Optionally Typed Rubyでは型はアノテーションですらなく単なるコメントです。checked modeは存在せず付けた型は完全に無視され、あってもなくても動作には一切影響がありません。悪しからずご了承ください。
*** parse.y.1.9.3-p0 2011-11-05 12:38:44.000000000 +0900 --- parse.y 2011-11-05 11:57:59.000000000 +0900 *************** top_stmt : stmt *** 877,911 **** } ; ! bodystmt : compstmt opt_rescue opt_else opt_ensure { /*%%%*/ ! $$ = $1; ! if ($2) { ! $$ = NEW_RESCUE($1, $2, $3); } ! else if ($3) { rb_warn0("else without rescue is useless"); ! $$ = block_append($$, $3); } ! if ($4) { if ($$) { ! $$ = NEW_ENSURE($$, $4); } else { ! $$ = block_append($4, NEW_NIL()); } } ! fixpos($$, $1); /*% $$ = dispatch4(bodystmt, - escape_Qundef($1), escape_Qundef($2), escape_Qundef($3), ! escape_Qundef($4)); %*/ } ; --- 877,912 ---- } ; ! bodystmt : opt_type ! compstmt opt_rescue opt_else opt_ensure { /*%%%*/ ! $$ = $2; ! if ($3) { ! $$ = NEW_RESCUE($2, $3, $4); } ! else if ($4) { rb_warn0("else without rescue is useless"); ! $$ = block_append($$, $4); } ! if ($5) { if ($$) { ! $$ = NEW_ENSURE($$, $5); } else { ! $$ = block_append($5, NEW_NIL()); } } ! fixpos($$, $2); /*% $$ = dispatch4(bodystmt, escape_Qundef($2), escape_Qundef($3), ! escape_Qundef($4), ! escape_Qundef($5)); %*/ } ; *************** stmt : keyword_alias fitem {lex_state = *** 989,994 **** --- 990,1004 ---- $$ = dispatch1(alias_error, $$); %*/ } + | lhs ':' cpath '=' arg + { + /*%%%*/ + value_expr($5); + $$ = node_assign($1, $5); + /*% + $$ = dispatch2(assign, $1, $5); + %*/ + } | keyword_undef undef_list { /*%%%*/ *************** cases : opt_else *** 3768,3773 **** --- 3778,3787 ---- | case_body ; + opt_type : ':' cpath + | none + ; + opt_rescue : keyword_rescue exc_list exc_var then compstmt opt_rescue *************** f_norm_arg : f_bad_arg *** 4546,4551 **** --- 4560,4566 ---- ; f_arg_item : f_norm_arg + opt_type { arg_var(get_id($1)); /*%%%*/