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言語みたく「変数 : 型」にしました。JavaDartの形式「型 変数」だとメソッド呼び出しと区別がつかないもので・・・。

文法

型付き変数宣言

変数名 : 型 = 値


型付きメソッド宣言

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));
                    /*%%%*/

*1:checked modeでコンパイルするとエラーか警告がでるみたい