お家で簡単!Quine(クワイン)の作り方
Quine(クワイン)という物をご存知でしょうか?
あるコードを実行すると自身のコードを出力するという不思議なプログラムのことです。
今日は皆さんとこれを作っていきたいと思います。
(料理番組のテーマっぽい音楽を脳内BGMで流しといてください。)
クワインはチューリング完全――つまり大抵のプログラミング言語であれば書くことが出来ます。自分自身のコードを参照できてしまうようなアセンブリみたいな芸当の出来る言語であれば考えるまでもなく簡単に実装できますが、そうじゃないと割りと面倒くさいことになります。
ここでは何となく手元にあったRubyで実装してみようと思います。まずは何はともあれ文字を表示する必要がありますから、こう。
puts "○○"
○○に自分自身を突っ込むことになるわけですが、そうするとputsが永遠に入れ子になって終わりません。そもそもよく考えるとn文字のコードを表示するのにn文字以上費やしていたらどうやっても追いつきませんね。これを解決するにはコードを表す文字列を圧縮する必要があります。圧縮する簡単な方法の1つは繰り返しです。いくらかの文字列を変数に突っ込み、その変数を複数回展開すれば文字数の問題を解決できそうです。というわけで変数を用意してみましょう。
s = "××" puts "○○"
s = "××" puts "s = \"#{s}\"\nputs \"○○\""
おっと突然複雑になったように見えますね。一行目でsに××を代入していますが、これを表示するには"s="という文字列を表示した後に実際にsの内容を展開すれば再現できます。ここから××と○○の内容を考えていくわけですがとりあえずこのコードを実行してみましょう。
実行結果
s = "××" puts "○○"
1行目は変化していないので××の部分には何を入れても良さそうです。一方2行目の○○には先程の2行目のputsに渡していたものを突っ込まなければならないようです。しかし、そうするとまた入れ子入れ子の堂々巡りにハマってしまいます。そこで××に何を入れても良いことを利用して○○の部分をsにします。つまりこうなる。
s = "××" puts "s = \"#{s}\"\nputs \"#{s}\""
実行してみる。
s = "××" puts "××"
これで××の内容を2行目のputsの内容と同じにすればOKですな。
s = "s = \"#{s}\"\nputs \"#{s}\"" puts "s = \"#{s}\"\nputs \"#{s}\""
これを実行するとエラーを突っ返されます。それもそのはずsを定義するのにsを参照しているわけですから怒られて当然です。一行目では式展開をされると困るわけです。ここでRubyの仕様を思い出してもらいますが、Rubyの文字列リテラルには"と'の2つがあり前者は式展開を行い後者は式展開をほとんど行わずそのままの文字列で解釈してくれるという仕様があります。これを活用しましょう。一行目のsの定義では無用な式展開を防ぐために'を使うことにします。
s = 's = \'#{s}\'\nputs \"#{s}\"' puts "s = '#{s}'\nputs \"#{s}\""
s = 's = '#{s}'\nputs \"#{s}\"' puts "s = '#{s}'\nputs \"#{s}\""
はい完成しましたね。お疲れ様で……ん?
s = 's = \'#{s}\'\nputs "#{s}"' puts "s = '#{s}'\nputs \"#{s}\""
s = 's = '#{s}'\nputs \"#{s}\"' puts "s = '#{s}'\nputs \"#{s}\""
よく見ると完成してない!!
1行目のシングルクォーテーションのエスケープが実行したら外れてしまっています。これを回避すべくgsubメソッドを使って消えた\を復活させましょう。
s = 's = \'#{s.gsub("\'","\\\\\\\\\'")}\'\nputs \"#{s}\"' puts "s = '#{s.gsub("'","\\\\'")}'\nputs \"#{s}\""
gsubの中の\がやたら増殖していますが、それはgsubの仕様のせいです*1。それでは実行してみましょう。
s = 's = \'#{s.gsub("\'","\\\\\'")}\'\nputs \"#{s}\"' puts "s = '#{s.gsub("'","\\\\'")}'\nputs \"#{s}\""
あー、1行目の\が減っちゃってますね。これもgsubで何とかしましょう。
s = 's = \'#{s.gsub("\\\\", "\\\\\\\\\\\\\\\\").gsub("\'", "\\\\\\\\\'")}\'\\nputs \\"#{s}\\"' puts "s = '#{s.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'")}'\nputs \"#{s}\""
gsubで1行目の\記号を増やすことを意識してさっきまで単体で書いていた\記号も\\に直しました。\記号が増殖して禍々しいことになっていますが、これを実行してみましょう。
s = 's = \'#{s.gsub("\\\\", "\\\\\\\\\\\\\\\\").gsub("\'", "\\\\\\\\\'")}\'\\nputs \\"#{s}\\"' puts "s = '#{s.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'")}'\nputs \"#{s}\""
色を付けてもう一度よく見比べてみよう
s = 's = \'#{s.gsub("\\\\", "\\\\\\\\\\\\\\\\").gsub("\'", "\\\\\\\\\'")}\'\\nputs \\"#{s}\\"' puts "s = '#{s.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'")}'\nputs \"#{s}\""
s = 's = \'#{s.gsub("\\\\", "\\\\\\\\\\\\\\\\").gsub("\'", "\\\\\\\\\'")}\'\\nputs \\"#{s}\\"' puts "s = '#{s.gsub("\\", "\\\\\\\\").gsub("'", "\\\\'")}'\nputs \"#{s}\""
大丈夫そうです。不安な人はこれを実行して実行結果をまたソースコードとして実行してみてください。
実はこんな風に特殊文字と戦わなくてもRubyであればたったの一行でクワインを表記できます。
eval a="puts'eval a='+a.inspect"
「最初からこっちを作ってくれよ」と言いたくなるかもしれないけれど、ここではクワインの考え方を理解してもらうために1番愚直な方法を採用しました。
では、その作り方をもう一度まとめておきましょう。
というわけでここまでくればもうどんな言語でもクワイン出来るはずですね。皆さんもご家庭でお好みの言語を使ってクワインを作ってみてください。
最後にC言語(?)で書かれた秀逸なクワインを置いてこの記事の締めとします。*3