quineとは
quineとは「自分自身の文字列を出力するプログラム」のことです。
wikiにはいくつかの言語で実装されたquineのサンプルがあります。
この記事ではelixirでQuineを実装しようと思います。
quineの実装
あなたの知らない超絶技巧プログラミングの世界 という本にQuineの基本的な書き方が載っていました。
Quineの基本的な書き方を引用します。
(1) 元のプログラム文字列の種となる文字列sを用意する。
(2) s自身を使ってsの中の一部を適切に書き換え、元のプログラム文字列を再構築する
(3) 出力する
(2)がややこしいので、(1)と(3)のみを実装したものから始めます。
本ではrubyで説明されていました。
s = "..."; print s
elixirもここから実装を始めてみます。
s="...";s|>IO.puts
ここから(2)の部分を追加していきます。
まずは「…」に自分自身を埋め込みます。
s="s=\"...\";s|>IO.puts";s|>IO.puts
"(ダブルクォテーション)はエスケープしています。
iex> s="s=\"...\";s|>IO.puts";s|>IO.puts
s="...";s|>IO.puts
:ok
このままでは、変数sしか出力できないので、「…」をsで置換します。
rubyではsubを使用していました。
elixirではString.replaceを使用します。
s="s=\"...\";s|>IO.puts";String.replace(s,"...",s,global: false)|>IO.puts
iex> s="s=\"...\";s|>IO.puts";String.replace(s,"...",s,global: false)|>IO.puts
s="s="...";s|>IO.puts";s|>IO.puts
:ok
String.replaceを追加したので、変数sも変更します。
s="s=\"...\";String.replace(s,\"...\",s,global: false)|>IO.puts";String.replace(s,"...",s,global: false)|>IO.puts
iex> s="s=\"...\";String.replace(s,\"...\",s,global: false)|>IO.puts";String.replace(s,"...",s,global: false)|>IO.puts
s="s="...";String.replace(s,"...",s,global: false)|>IO.puts";String.replace(s,"...",s,global: false)|>IO.puts
:ok
ほぼ一致しましたが、"s=\"...\"
の部分が"s="..."
になっています。
rubyでも同様の問題があり、置換前にdumpを使って解消していました。
elixirではinspectを使用してみます。(変数sも調整します)
s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>IO.puts
iex> s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>IO.puts
s=""s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>IO.puts"";String.replace(s,"...",inspect(s),global: false)|>IO.puts
:ok
rubyではここで解決していました。
がelixirだとs="s=\"
の部分がs=""s=\"
になっています。
String.replace(\"\", \")
を追加すればいいのですが、このまま追加すると変数sの調整ができなくなります。
そこでASCIIコードを利用して、String.replace(<<34, 34>>, <<34>>)
を追加します。
s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts
iex> s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts
s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts
:ok
rubyより1つ処理が増えましたが、Quineを実装できました。
実行結果の確認
Quineが実装できたどうかを確認します。
quine.exsを作成します。
s="s=\"...\";String.replace(s,\"...\",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts";String.replace(s,"...",inspect(s),global: false)|>String.replace(<<34, 34>>, <<34>>)|>IO.puts
出力した結果をファイルに保存し比較します。
elixir quine.exs > quine.txt
diff quine.exs quine.txt
また出力したquine.txtもelixirとして実行できます。
cp quine.txt quine2.exs
elixir quine2.exs
その他のQuine
elixirでQuineの実装例を調べてみました。
いくつか実装例がありますが、これがelixirらしくて好きでした。
"fn x->IO.puts~s(\#{inspect x}|>\#{x}) end.()"|>fn x->IO.puts~s(#{inspect x}|>#{x}) end.()