parse.y の歩き方 - ワシの Ruby は 4 式まであるぞ -

編集注。この記事は 2010 年に開催された RubyKaigi2010 での LT をるびま誌上で再現したものです。

parse.y の歩き方 - ワシの Ruby は 4 式まであるぞ -

まず、RubyKaigi の LT では最初に「Google Wave 入門」をさせていただき、おかげさまでジュンク堂 RubyKaigi 支店の「Google Wave 入門」は売り切れとなりました。この場を借りてお礼のふりをした再度の宣伝をさせていただきます。右の画像がリンクになっていますので、よろしくお願いします。

さて、本 LT では元々はタイトルの通り私が parse.y を少しだけ弄って作った少し変わった Ruby を紹介する予定でしたが、資料を作っているうちになんとなく趣旨が変わってきてしまったのでタイトルを変更させていただきました。また、8 月は何かと気苦労が多くて 4 つ目を作る暇がなかったのでサブタイトルも修正します。

Ruby 幼年期の終わり - ワシの Ruby は 3 式までしかないぞ -

Ruby は 1996 年 12 月 25 日に Ver. 1.0 がリリースされ、その後も 2002 年のクリスマスには 1.6.8 、2004 年のクリスマスに 1.8.2 、2007 年もぎりぎり地球上のどこかがクリスマスであるうちに 1.9.0 がリリースされました。そして昨年、2009 年のクリスマスです。勘違いしている人も多いようですが、実は昨年のクリスマスにも新しい Ruby がリリースされています。それが XRuby です。

XRuby

XRuby の X は XML を意味し、その名の通り XRuby では XML リテラルが利用可能になっています。

http://github.com/technohippy/xruby

phone_book = <?xml>
  <phonebook>
    <entry>
      <name>Burak</name> 
      <phone where="work">  +41 21 693 68 67</phone>
      <phone where="mobile">+41 79 602 23 23</phone>
    </entry>
  </phonebook>
</xml>

puts phone_book.class.name
#=> REXML::Document

puts phone_book.elements["//phone[@where='mobile']"].text
#=> +41 79 602 23 23

puts phone_book.elements["//descr/a"].attribute('href')
#=> http://acme.org

実装は parse.y のヒアドキュメントのところをいろいろとコピペしてちょっと修正しています。

xml     : tHEREXML_BEG string_contents tSTRING_END
            {
            /*%%%*/
                NODE *node = $2;
                if (!node) node = NEW_STR(STR_NEW0());
                $$ = NEW_CALL(node, rb_intern("xml"), 0);
            /*%
                $$ = dispatch1(xstring_literal, $2);
            %*/
            }
        ;

このクリスマス Ruby のおかげで C 言語がよく分かってなくても、コピペと想像力と気合いで Ruby の改造は意外となんとかなることが分かったので、XRuby 以外にもいくつか作ってみました。

ORuby

二つ目は ORuby です。O は Objective です。メソッド呼び出しをオブジェクティブに書けます。普通の Ruby と比較するとこんな感じ。

http://github.com/technohippy/oruby

# 普通の Ruby
require 'date'
birthday = Date.new(1993,2,24)
if birthday == Date.today
    puts "Happy birthday, Ruby!"
end

 # Objective Ruby
 require 'date'
 birthday = %o{ Date new:1993 month:2 day:24 }
 %o{ (birthday = Date today) ifTrue:[Kernel puts:'Happy'] }

%o{…} が今回導入されたオブジェクティブリテラルで、オブジェクティブリテラル内では Ruby のメソッド呼び出しをまるで Objective-C のメッセージ式のように書くことができます。birthday という変数がオブジェクティブリテラルの内外でやり取りできているのが本例の見所です。

これだけだといまいちなのでさらに進んだ例。

# 普通の Ruby
class MyClass
  def my_method(num)
    while 0 < num
      if num % 2 == 0
        puts num
      end
      num -= 1
    end
  end
end
MyClass.new.my_method(10)

 %o{ Object subclass: 'MyClass'. }
 %o{ MyClass compile: '
   my_method: num 
     [num := num - 1. 0 < num.] whileTrue:[ 
       val := (num % 2).
       (val = 0) ifTrue: [
         Kernel puts: num.].].'}
 MyClass.new.my_method(10)

クラス定義もオブジェクティブに書く事ができます。見所はオブジェクティブに定義したクラスとメソッドを、オブジェクティブリテラルの外の普通の Ruby の構文で利用できているとこです。

実装はこんな感じです。

objective : tOBJECTIVE_BEG xstring_contents tSTRING_END
              {
              /*%%%*/
                  NODE *node = $2;
                  if (!node) node = NEW_STR(STR_NEW0());
                  $$ = NEW_CALL(node, rb_intern("smalltalk"),
                    NEW_LIST(NEW_VCALL(rb_intern("binding"))));
              /*% 
                  $$ = dispatch1(xstring_literal, $2); 
              %*/
              }
          ;

%x{} とかをコピペしてゴニョゴニョしました。このオブジェクティブリテラルは Smalltalk で著名な梅澤さんにも「Objective Ruby、もう標準でRubyに入っていていいんじゃないかと思いました。」と言われるほど大きく期待されています。Ruby 2.0 の目玉機能としていかがでしょう?

FRuby

最後のオレオレ Ruby は FRuby です。F は Fuzzy の略で、その名のとおりなんとなくあいまいに実行したりしなかったりする条件文が利用できます。

http://github.com/technohippy/fruby

count = 0
1000.times do
  if true then
    count += 1
  end
end
puts count #=> 1000 (100%)

count = 0
1000.times do
  if true probably
    count += 1 
  end
end
puts count #=> 839 (80%)

count = 0
1000.times do
  if true maybe
    count += 1
  end
end
puts count #=> 471 (50%)

count = 0
1000.times do
  if true perhaps
    count += 1 
  end
end
puts count #=> 188 (20%)

FRuby では if then 以外に確度に応じて probably、maybe、perhaps が利用可能です。

実装はこんな感じになります。

static NODE* 
cond_gen(struct parser_params *parser, NODE *node, float maybe)
{
    NODE *mcond;
    if (node == 0) return 0;
    if (maybe == 1.0) { // then
      return cond0(parser, node);
    }
    else { // perhaps, maybe, probably
      mcond = call_bin_op(NEW_LIT(rb_float_new(maybe)), tGEQ, NEW_FCALL(rb_intern("rand"), 0));
      return cond_gen(parser, NEW_NODE(NODE_AND, node, mcond, 0), 1.0); 
    }
}

if then を処理してるっぽい関数に無理やり確度を表す引数を追加して、条件文を Fuzzy な感じにしました。C 言語を知らなくても NEW_FCALL とか使えば普通に Ruby のメソッドが呼べるので素敵です。

この FRuby は Ruby が海外で広まり始めたころに if not を unless と書けることがウケてたと聞いて、こう言うのもウケがいいんじゃないかなと思って作ったものだったので、発表で期待通りに海外組が笑ってくれたのがなかなかうれしかったです。

まとめ

以上のとおり parse.y を適当にコピペして新しい Ruby を作るのは意外と簡単だということが分かりました。コツ (?) は

  • 似ている機能を見つけてコピペする
  • C 言語が分からないときは NEW_CALL とか NEW_FCALL とか駆使して Ruby を使う
  • ややこしそうな機能は Ruby で作って prelude.rb に突っ込む

の 3 つです (ただし上記にいくら習熟しても永遠にコミッターにはなれません) 。

幼年期の終わり

これまで我々はずっとクリスマスにサンタクロースの格好をした Matz にプレゼントをもらってばかりでした。Ruby が生まれて今年で 17 年。17 年という年月は Ruby が生まれたときに小学生だったとしても今では家庭を持ち子どもがいてもおかしくない、それだけの年月です。そろそろ私たち Ruby ユーザーも幼年期を終え、クリスマスプレゼントを受け取る側から、贈る側へと変わる時ではないでしょうか。ネタ次第では Ruby の改造もわりと簡単だということを我々は既に知っています。ということで……

今年からクリスマスはユーザーが勝手 Ruby を作って公開する日にしませんか?

著者について

Ando Yasushi (Seesaa Inc.) たぶん史上最後の Google Wave 本の著者。あとおっぱいとかカリスマとか