クワインとは次のプログラムのこと
- コードからある文字列を出力する
- それが全くコード自身と同じになる
これをクワイン=Quineというらしいです。
そこでJavaScriptでクワインを作ってみました。
ほぼググらずに完全に自作したものを紹介します。
このページの目次
そもそもクワイン(Quine)とは何なのか。定義と詳細
その説明はググればいくらでも出てきます。
▼ 自称モノ知り博士のWikipedia君の解説
クワイン(英: Quine)は、コンピュータプログラムの一種で、自身のソースコードと完全に同じ文字列を出力するプログラムである。娯楽として、プログラマが任意のプログラミング言語での最短クワインを書くことがある。プログラムを出力するプログラムだと見れば、クワインのプログラミングはメタプログラミングの一種である。
「自身のソースコードと完全に同じ文字列」
これがクワインの定義とのこと
メタプログラミングの一種でもあるようです。
定義が分かったので早速JavaScriptで挑戦してみます。
世界一簡単な7文字のJSクワインがこれ!!
世界一簡単にクワインを書く方法
なんとJSなら7文字で書けます。
▼ そのコードというがこれ!!
1 |
'Quine' |
▼ コンソール出力結果
1 |
'Quine' |
JavaScriptの仕様なのか分からないけど、開発者ツールのコンソールで数値・文字列などを実行するとそのまま出力してくれます。
大事なポイントは値で解釈された文字列だからシングルクオート付き(
'...' )で出力されること。
この点は後述するクワインでも重要になってきます。
たった7文字でクワインがかけるJSはすごいです!
ただ先ほどのクワインのWikiをよく読んでみると…
▼ 定義に依るとクワインではない模様
要件の直感的な説明からは、いくつかのチート的な解がある。例えば、入力をそのまま出力するだけのプログラム(Unixではcatというプログラムが利用される)の入力を、そのプログラムのソースファイルとするとか、いくつかのプログラミング言語(の処理系)は空のソースコードを受け取って、何も行わない、という動作をするので、それを利用する手もある。そのような空のプログラムがIOCCCで「規則のはなはだしい悪用」賞を受賞したこともある。以上のようなプログラムはいずれも通常、この問題を解いたものとはみなされない。
これはチート的な解みたい
王道のconsole.logを使ったクワインを作ってみる
やっぱりconsole.logを使うのが王道
ということでそのクワインを考えてみました。
▼ 参考にしたのが次のPythonのクワインです。
1 |
s='s=%r;print(s%%s)';print(s%s) |
Pythonならここまで短く書けるのか…
でもJavaScriptではprintは使えません。
console.logだけでやるのは骨が折れます。
でも次のような感じで作れました。
1.コード格納部分と出力部分に分けてコード作成
先のPythonコードでも以下をやってます。
- コードの出力部分をまず宣言
- そのあとトリッキーにそれを出力
この要件さえ満たせればクワインを作れます。
だから適当に次のようなJSコードを書いてみました。
▼ 適当に作ったコード。当然クワインではない
1 |
var s="var s=***;console.log(s)";console.log(s) |
▼ コンソール出力結果
1 |
var s=***;console.log(s) |
ひとまずコード部分を var s="..." と宣言したはいいものの、そのコード部分で自分自身を埋め込まないといけなくなります。
そしてそれが一筋縄でいかないから難しい
2.クワインまであと一歩のところまで出来たが…
そこで色々改良していきました。
▼ 最終的にこういうコードになった
1 |
var s="var s=%s;console.log(s.replace('%s',s))";console.log(s.replace('%s',s)) |
▼ コンソール出力結果
1 |
var s=var s=%s;console.log(s.replace('%s',s));console.log(s.replace('%s',s)) |
とっても惜しいけど違う!
C言語ライクに %s のような目印を置き、直前に宣言したコード格納変数とリプレースする処理を書いてみました。それで大体はいけます。
ほぼ完ぺきに見えたけど盲点が1つありました。
それは出力結果を見ると var s=var s... みたいにダブルクオートがコンソール出力時に現れないことです。その回避がクワインの面白い所です。
3.ダブルクオートを補足してクワイン完成!
そこで次の方法で回避してみました。
- ダブルクオートを変数として宣言する
- 出力時にダブルクオートを補足する
具体的には次のようなコードになりました。
▼ 完成したクワインコード
1 |
var q=String.fromCharCode(34);var s="var q=String.fromCharCode(34);var s=%q%s%q;console.log(s.replace('%q%s%q',q+s+q))";console.log(s.replace('%q%s%q',q+s+q)) |
▼ 出力結果。コードと全く同じ
1 |
var q=String.fromCharCode(34);var s="var q=String.fromCharCode(34);var s=%q%s%q;console.log(s.replace('%q%s%q',q+s+q))";console.log(s.replace('%q%s%q',q+s+q)) |
まずダブルクオートを
var q=String.fromCharCode(34); のように文字コードから変数として宣言。
この工夫=「文字列表現に使ってる文字のエスケープ」がクワインでは超大事なポイントです。
そしてコード格納部分の
var s=%q%s%q に対して
s.replace('%q%s%q',q+s+q) と置換してます。
これでダブルクオートが消える問題を回避してるってこと
意外と複雑になったけどクワインが完成。
まるで知恵の輪を外してるような感覚。大雑把にコード格納部分・出力部分に分け、そのあと辻褄合わせをしていくのがクワイン作成で面白かった所です。
ついでにアロー関数を使ったクワインも作ってみた
自作が成功したので他人のクワインも見てみました。
色々なクワインがあってそれを見るのも興味深いです。
▼ その中で興味をひかれたのが次のクワイン
1 |
(function a(){console.log("("+a+")()")})() |
▼ この記事に載ってた
JavaScriptでは関数を文字列として展開できます。
それを利用したクワインですね。なるほど
これはアロー関数を使うともっと短くできます。
▼ こんなコードに改造してみた
1 |
(_=()=>console.log('(_='+_+')()'))() |
▼ コンソール出力結果
1 |
(_=()=>console.log('(_='+_+')()'))() |
記号が '+_+' で顔文字っぽい笑。
メタプログミングっぽい感じでカッコいいし、
こういうのも面白い・興味深いなと思いました。
以上、JSでクワインを書いてみたでした。ではまた