Ruby本体の「僕の作品」を修正した話
昨年秋のRubyKaigiに参加したとき、コミッタである中田さんから私の書いたコードがRuby trunkで壊れているという連絡をいただいた。そこで、それを修正するプルリクエストを書いた。
といっても、diffは以下の1行だけ。
-_ { return }
+_ { yield }
一体何を直したか分かるだろうか?
TRICK 2013
ヒントはファイル名にある。
trick2013というのは、過去に行われた「普通でないRubyプログラムのコンテスト」だ。IOCCCのRuby版、といえば伝わる人には伝わるだろう。
私は2つのプログラムを応募して、そのうちの1つが4位に入賞した。誰が言い出したのか、コンテストの特典として入賞した作品はRubyのsampleディレクトリにコミットされるというものがあり、かくして私の「普通でないRubyプログラム」がRubyのソースコードに同梱されることになった。
entry.rb
入賞した作品というのがこれだ。
def _(&b)$><<->(x){x ? (String===x ?x.upcase:
(Class===x ? x : x.class).name[$a?0:($a=5)]):
" "}[ begin b[];rescue Exception;$!;end ] end
_ { return }
_ { method(:p).unbind }
_ { eval "{ " }
_ { Thread.current.join }
_ { nil }
_ { select }
_ { ruby }
_ { self.class }
_ { Thread.current.group }
_ { nil.to_h }
_ { "\xFF".encode("big5") }
_ { raise }
_ { [0][1] }
_ { Regexp.compile "*" }
_ { RUBY_COPYRIGHT[32] }
_ { binding }
_ { :s.class.name[1] }
_ { warn }
_ { [a: :b][0] }
_ { methods }
_ { IO.class }
_ { {}.fetch(0) }
_ { open " " }
_ { 1000000.chr }
実行すると以下のように出力される。
$ ruby entry.rb
JUST ANOTHER RUBY HACKER
「JUST ANOTHER RUBY HACKER」というのはこの手のプログラムによくある出力で、もとはPerlの文化だと思われる。
仕組み
動作を簡単に解説しておく。まず、最初の3行で_
というメソッドを定義し、後半ではそれを使って1行ごとに1文字を出力している。
文字の生成方法は、3行目のrescue Exception
が肝で、各ブロック内で例外を起こし、その名前から文字を拾うというのが基本戦略だ。以下を見れば、頭文字が文章になっていることが分かるだろう。
_ { return } # Local"J"umpError
_ { method(:p).unbind } # "U"nboundMethod
_ { eval "{ " } # "S"yntaxError
_ { Thread.current.join } # "T"hreadError
_ { nil } #
_ { select } # "A"rgumentError
_ { ruby } # "N"ameError
_ { self.class } # "O"bject
_ { Thread.current.group } # "T"hreadGroup
_ { nil.to_h } # "H"ash
_ { "\xFF".encode("big5") } # "E"ncoding::InvalidByteSequenceError
_ { raise } # "R"untimeError
_ { [0][1] } #
_ { Regexp.compile "*" } # "R"egexpError
_ { RUBY_COPYRIGHT[32] } # "U"
_ { binding } # "B"inding
_ { :s.class.name[1] } # "Y"
_ { warn } #
_ { [a: :b][0] } # "H"ash
_ { methods } # "A"rray
_ { IO.class } # "C"lass
_ { {}.fetch(0) } # "K"eyError
_ { open " " } # "E"rrno::ENOENT
_ { 1000000.chr } # "R"angeError
Ruby 2.5
ところが昨年秋にRuby trunkに入った修正で、このプログラムが動かなくなった。1行目のブロック内でのreturnが許容されるようになったので、LocalJumpErrorが起こらなくなってしまったのだ。
まあこんなプログラムが動かなくなっても問題はないのだけど、しかしsample以下にコミットされているプログラムが"正しく"実行できないというのは宜しくない気もする。
そういうわけで、作品性を損なわない形でRuby 2.5用の修正を施したのが、冒頭の
-_ { return }
+_ { yield }
というプルリクエストの正体だったのだ。
TRICK 2018
さて、どうして今ごろになってこんな記事を書いているかというと、このコンテストが再び行われているからだ。
締切は今月末の3/31。発表は5月のRubyKaigi 2018 仙台で行われる。"FINAL"と付いている通り、mameさん主催での開催は次回の予定はないとのことなので、まだ応募していないアイデアがある人は応募してほしいし、そうでない人も応募してほしい。過去の作品については以下が参考になるだろう。