RubyKaigi 2025 にて、いかにして超絶技巧・意味不明な Ruby コードを書けるかを競うコンテスト TRICK 2025 が開催されました。今回、筆者が提出した作品が『最も自然で賞』(“Most Natural”) をいただきました。作品をつくる過程で体感した様々な発見・学びを元に、本記事では「超絶技巧 Ruby 意味不明コーディング」の始め方をお伝えしたいと思います。
TRICK の正式名称は、”Transcendental Ruby Imbroglio Contest for rubyKaigi”(“超絶技巧 Ruby 意味不明コンテスト for RubyKaigi”) です。2013 年以降、数年に一度のペースで不定期に開催されており、2025 年は第 5 回目にあたります。初回の 2013 年に、TRICK の詳しい説明を含めたコンテスト結果がるびまの記事として掲載されていましたので、ぜひご覧ください。
Ruby は書いていて大変「たのしい」言語である、ということは読者のみなさんも実感されていることと思います。「たのしい」という感覚の一部は、読みやすい・書きやすい、という言葉でも表現できるでしょう。TRICK で紹介される Ruby のソースコードの大半はあえて難読化されており、読みやすさ・書きやすさという類のたのしさからは完全に逆走していますが、ソースコード全体を「鑑賞対象」として認知したとき、新しい「たのしい」がそこに芽生えています。TRICK の募集ルール には次のような表記があります。それゆえに、いずれの作品も Rubyist たちに驚き・興奮・笑いを提供してくれます。
Your entry must bring the judges a surprise, excitement, and/or laughter.
TRICK の発表に刺激を受けて、自分でも書いてみたいという憧れが芽生えたのならば、思い切って超絶技巧・意味不明な Ruby コーディングへ踏み出してみましょう。
まずは第一に、Ruby でどんなアクロバティックなことが出来るのかの例をたくさん浴びてみるのをおすすめします。過去の TRICK 入賞作品はすべてリポジトリにて公開されています (https://github.com/tric)。 それぞれを実行したり、ソースコード自体を鑑賞したりして楽しむなかで、自分自身の興味を強く喚起する作品をいくつか見つけてみましょう。興味の種となる作品を発見したら、それがどのように動いているのかが気になってくると思います。
次は「教科書」の出番です。書籍「あなたの知らない超絶技巧プログラミングの世界」(以下、「超絶技巧本」 ) は、TRICK 審査員のお一人でもある遠藤さんによる著書で、Ruby による超絶技巧的な作品の作り方が載っている唯一無二の書籍と言えるでしょう。書籍の前半 (第 1 章) が作品集となっており、後半 (第 2 章以降〜) には技巧の詳細な解説が掲載されています。興味の湧いた作品のソースコードと照らし合わせることで、どうやって動いているのかという疑問への答えが少しずつ見えてくるでしょう。
自らの興味の種の輪郭がみえたら、小さいサンプルを動かしてみましょう。超絶技巧本の第 2 章以降にあるサンプルコードは十分手打ちできるものが多いですし、または STEP1 でチェックした作品群から、理解できる要素部分を抜き出して動かしてみるのも面白いです。
るびま読者のみなさんも、日常の仕事や趣味のコーディング活動で既に体感されているかと思いますが、ひとつ書けば「もっとこうしたい」「もっとこうできる」という発見が自然と立ち現れてくるというのは往々にしてあるものです。その気持ちに逆らわず、たのしく気ままに書いていくことで、オリジナリティのある作品に仕上がっていきます。組み上げていく途中で浮かぶアイデアを実現するための手段については、やはり超絶技巧本と過去の作品群から大いにヒントを得ることができます。時々、キリのいいタイミングで Git のコミットを詰むことだけは忘れないようにしていきたいものです。
ある程度満足いく出力結果が完成したら、改めてソースコードを俯瞰してみましょう。TRICK では、ソースコード自体がなんらかの形や文字を模しているような作品も多くみられます。もし、出来上がったソースコード自体は普通の Ruby であるならば、成形で遊んでみるチャンスです。
大まかな成形の流れとしては「別途で成形用のソースコードを準備する」「成形対象のソースコードを圧縮に耐えられる表記にする」の 2 つの手順が必要です1。また、TRICK への提出を目指す場合には文字数制限にも注意が必要です。完成したソースコードのボリュームが大きい場合には、あの手この手で短いコードを目指す必要があります。見慣れた Ruby の書きやすさ・読みやすさからは、この時点から徹底的に離れていくことになるでしょう。しかしその過程は、Ruby という言語の柔軟さ・懐の深さを知ることのできる面白い機会でもあります。
筆者が TRICK 提出作を書いた際には、圧縮作業の中で、業務では絶対に手を出さないような書き方にチャレンジすることになりました。特徴的な例をいくつかご紹介します。
";" をつける圧縮するということは、改行を用いた見やすさを全て捨てねばなりません。ゆえに、普段の業務では滅多にしないセミコロン ( ";" ) の多用をすることになります。
# 次のような定義をしたいとき
A = 1
B = 2
C = 3
# NG 動かない
A=1B=2C=3
# OK 動く
A=1;B=2;C=3;
ソースコードを成形する場合、この処理は必須です。TRICK 提出作を完成させた後しばらく、指が勝手に末尾に ";" をつけるクセがついてしまいましたが、半日程度で治ってホッとしました。
プログラミングにおける基礎のひとつとして、if 文は欠かせないところでしょう。これを几帳面に、かつ、圧縮しても問題の無いように書くと大変冗長になってしまいます。
x=rand(10);if(x<5);x=0;end;
これを "&&" を使って次のようにしてみました。"&&" の演算子は、左辺が真だったときのみ右辺の評価をする2ので、このような書き方ができます。これで 7 文字の削減に成功しました。
x=rand(10);x<5&&x=0;
case 文は特定の値に対する分岐を書く際には便利ですが、TRICK 提出作品を目指すにあたっては、文字分量が多く活用がやや困難な印象を受けました。
case rand(10)
when 0..2 then "a" # 30%の確率で 'a' を選択
when 3..5 then "b" # 30%の確率で 'b' を選択
when 6..8 then "c" # 30%の確率で 'c' を選択
else nil # 10%の確率でなにも選択しない
end
これを圧倒的に短くしてみましょう。
"aaabbbccc"[rand(10)]
String は self[nth] と書くことで、nth 番目の文字を返します3。そのため、数に対して 1 文字返すという処理に限った話ではありますが、このような書き方ができます。筆者の場合は、10%単位の割合で特定の文字を採用する、といった処理の際に有用でした。
今回、筆者が TRICK 2025 に提出した作品は、成形前・後のコードをGitHub に公開しています。コードの中でも特に顕著に圧縮に成功した箇所を抜粋して紹介します。
if i == 0
s[el_number_y][el_number_x] = "*"
else
dy = (p[1] - points[i-1][1])
dx = (p[0] - points[i-1][0])
mark = "|" if dx.zero?
scope = dy / dx
dec = ((p[1].round(1) - p[1].floor) * 10).to_i
mark ||= case dec
when 0..2 then (dy.zero? ? '^' : '`')
when 3..7 then nil
else (dy.zero? ? '_' : ',')
end
mark ||= case scope.round(1)
when 0.0 .. 0.9 then '*'
when 1.0 .. 1.9 then '\\'
else ';'
end
s[el_number_y][el_number_x] = mark
end
※修正の過程で仕様の一部を変更しています。
if (i == 0) then;
s[ey][ex] = "*";
else;
dy = (p[1] - ps[i-1][1]);
dx = (p[0] - ps[i-1][0]);
(dx.zero?) && (m = "!");
dec = ((p[1].round(1) - p[1].floor) * 10).to_i;
m ||= ("```TRICK2025"[dec]) || ",";
(m =~ /\w/)&&(m = "*;"[(dy/dx).round] || 'l');
s[ey][ex] = m;
end;
成形前・後で、231 文字の削減に成功しています。筆者にとってリファクタリングはかなり好ましい作業のひとつですが、文字数の圧倒的削減という目標を掲げた上での修正作業には、普段の業務とは異なる脳の領域を働かせた感覚がありました。困難に対して創意工夫を凝らす作業に「たのしい」を感じるタイプの人は、きっと超絶技巧プログラミングからも「たのしい」を感じとれるでしょう。
ここまで、TRICK への作品提出に至るまでの工程をある程度ご紹介しましたが、いずれも、まだ普段目にしている書き方からの大幅な剥離はないように思います。さらにもう少しだけ、深淵を覗くことにトライしてみましょう。例えば、「Ruby」と書かずに「Ruby」と出力するコードを考えてみます。下記は Ruby 3.4.4 で動作します。また、平易にご紹介するために一部の説明を簡略化している場合がありますが、ご了承ください。
[82, 117, 98, 121].map {|n| '' << n }.join
#=> "Ruby"
こちらのコードは見た目の印象から、配列内の数字がそれぞれ 1 文字に対応していそうだぞ、ということは感じとれるでしょう。 82、117、98、121 は、それぞれ “R”、”u”、”b”、”y” の ASCII コードです。
そして、 String#<< は、数値である n を n.chr として末尾に追加します5。そのため、空文字に対して追加処理すると ( '' << n )、結果的に各 ASCII コードに対応する 1 文字を得ることができます。["R","u","b","y"] という配列ができたら、それを join することで "Ruby" が出力されます。
もっと奇妙なコードを考えてみましょう。
["52756279"].pack("H*")
#=> "Ruby"
Array#pack 6 は普段の業務で取り扱う機会のある方は少ないかもしれません。このメソッドは、平たくいうと、配列の中身の値をバイナリとして変換します。pack() の引数は変換するためのルールを指定しています。
今回のルール "H*" は、16 進文字列として変換することを指します。"52756279" という文字を 16 進のバイナリとして解釈するとき、1 バイト (8 ビット) 分は 2 文字となるため、2 文字毎に変換され、次のようになります。
"52".to_i(16) #=> 82
"75".to_i(16) #=> 117
"62".to_i(16) #=> 98
"79".to_i(16) #=> 121
この 4 つの数字は、先程確認した ASCII コードと一致します。そのため、"Ruby" という文字列を出力することができます7。
ここまでの流れを経て、最終的に [82, 117, 98, 121] の数字を得られさえすれば、どんなに変わったことをしても "Ruby" という文字列に辿り着けそうだということがわかってきました。ならばもっと遊んでみましょう。
%^^ << (66|16) << (48|69) << (64|34) << (48|73)
#=> "Ruby"
(数字|数字) という書き方が特徴的ですね。これ 1 つで 1 文字ということは雰囲気から明らかです。では、この書き方は何をしているのかを調べると8、数値を 2 進数と解釈して論理和を得ていることがわかります。具体的に、まずは (66|16) を考えてみましょう。それぞれの数字の 2 進数表現を確認します。
66.to_s(2).rjust(8,"0") #=> "01000010"
16.to_s(2).rjust(8,"0") #=> "00010000"
この 2 進数の組み合わせの論理和 ( 同じ桁のどちらかが 1 であれば、1 とする ) は、"01010010" となります。これを 10 進数として解釈すると、82 という値が得られます。
"01010010".to_i(2) #=> 82 ( ASCII コードでの "R" )
また、%^^ という表記も興味深いです。これは%記法によるダブルクォート文字列の空文字 "" と同じです。%記法は、%() のような表記方法のほうが直感的でわかりやすいと感じますが、()部分には任意の非英数字が使えるので、%^^ という書き方でも問題なく動きます。9
%^^ #=> ""
%^hello, world^ #=> "hello, world"
パッと見で、意味不明だ・奇妙だと感じたソースコードも、ひとつひとつ紐解いていくと「なるほど!」と膝を打てるものになったのではないでしょうか。さらに難易度の高い実践は、「超絶技巧本」や過去の TRICK 入賞作品にて見ることができます。これらにもじっくり向き合ってみると、自分の作品に応用できる発見につながるはずです。TRICK で感動を覚えた方は、ぜひ取り組んでみてください。
超絶技巧 Ruby 意味不明コーディングのファンのひとりとして、さらに多くの作品との出会いをたのしみにしています!
本記事の執筆は、「Rubyist なにかやる合宿 2025」での neko314 さんからのお声がけと、参加者のみなさんからの励ましによって実現しました。貴重な機会をいただき、ありがとうございました!
@beta_chelsea: 教育系スタートアップにて、Rails 製の自社サービス開発保守を担当しています。他活動として、Rails Gilrs コーチ経験、フィヨルドブートキャンプメンター参加、Kaigi on Rails Team 参加。
ソースコードを成形する詳しい方法については、超絶技巧本の第 2 章「アスキーアートでプログラミング」を参照してください。 ↩
演算子 "&&" について: 演算子式 (Ruby 3.4 リファレンスマニュアル) ↩
文字列に対する self[nth] の書き方について: String#[] (Ruby 3.4 リファレンスマニュアル) ↩
改めて考えると、もう少し圧縮する余地もあったように思います。また、後日になって絶対に実行されない無駄記述が発生していたことに気付きました。どこでしょう? ↩
'' << n の処理について: String#« (Ruby 3.4 リファレンスマニュアル) ↩
"Ruby"という文字列を Array#pack を使っていかにして表示させるか、という試みはリファレンスマニュアル6内の「使用例」にも多数掲載されています。 ↩
(数字|数字)の処理について: Integer#| (Ruby 3.4 リファレンスマニュアル) ↩
%^^ の処理について: リテラル (Ruby 3.4 リファレンスマニュアル) ↩