
クワインとは、入力を一切受け取らず、自身のソースコードのコピーのみを出力するコンピュータプログラムです。計算可能性理論やコンピュータ科学の文献では、これらのプログラムを「自己複製プログラム」、「自己増殖プログラム」、「自己複製プログラム」といった 標準的な用語で表現しています。
クインとは、実行環境をプログラムをその出力に変換する関数とみなした場合の、その実行環境の不動点である。クインは、クリーネの再帰定理の直接的な帰結として、チューリング完全なプログラミング言語であればどこでも実現可能である。プログラマーは、娯楽として、特定のプログラミング言語で可能な限り最短のクインを開発しようとすることがある。
「クワイン」という名前は、ダグラス・ホフスタッターが1979年に出版した人気科学書『ゲーデル、エッシャー、バッハ』の中で、間接的な自己言及について広範な研究を行った哲学者ウィラード・ヴァン・オーマン・クワイン(1908年 - 2000年)に敬意を表して名付けられました。特に、クワインのパラドックスとして知られる次のようなパラドックスを生み出す表現にちなんで名付けられました。
「引用符が先行すると虚偽となる」は引用符が先行すると虚偽となる。
ジョン・フォン・ノイマンは1940年代に自己増殖オートマトンを理論化しました。その後、ポール・ブラットリーとジャン・ミロは1972年に論文「コンピュータレクリエーション:自己増殖オートマトン」で自己増殖オートマトンについて論じました。 [ 1 ]ブラットリーが自己増殖プログラムに興味を持つようになったのは、1960年代にエディンバラ大学の講師兼研究者であるハミッシュ・デュワーがエディンバラ大学でAtlas Autocode に書いた最初の自己増殖プログラムを見たことがきっかけでした。
GNUアフェロ一般公衆利用許諾書の「ソースコードのダウンロード」要件は、クワインの考え方に基づいています。[ 2 ]
2020年現在、主要なプログラミング言語における最短の非自明なクイン(「適切な」クイン)は、21文字のJavaScript ES6プログラムである($=_=>`($=${$})()`)()。[ 3 ]
一般的に、どのプログラミング言語でもクインを作成する方法は、プログラム内に2つの部分、つまり実際の印刷を行うためのコードと、コードのテキスト形式を表すデータを配置することです。コードは、データを使用してコードを印刷することで機能します(データがコードのテキスト形式を表しているため、これは理にかなっています)。また、単純な方法で処理されたデータを使用して、データ自体のテキスト表現を印刷することもできます。
以下に Python3 の 3 つの小さな例を示します。
# 例A. chr(39) == "'". a : str = 'a: str = {}{}{} ; print(a.format(chr(39), a, chr(39)))' ; print ( a . format ( chr ( 39 ), a , chr ( 39 )))# 例B. chr(39) == "'". b : str = 'b: str = %s%s%s ; print(b %% (chr(39), b, chr(39)))' ; print ( b % ( chr ( 39 ), b , chr ( 39 )))# 例 C. %r は自動的に引用符で囲みます。c : str = 'c: str = %r ; print(c %% c)' ; print ( c % c )次の古典的なJavaコード (Java 1.5) は、クワインの基本構造を示しています。
public class Quine { public static void main ( String [] args ) { char q = 34 ; // 引用符文字String [] l = { // ソースコードの配列"public class Quine {" , " public static void main(String[] args) {" , " char q = 34; // 引用符文字" , " String[] l = { // ソースコードの配列" , " " , " };" , " for (int i = 0; i < 4; i++) { // 開始コードを出力" , " System.out.println(l[i]);" , " }" , " for (int i = 0; i < l.length; i++) { // 文字列配列を出力" , " System.out.printf(\"%s%c%s%c,%n\", l[4], q, l[i], q);" , " }" , " for (int i = 5; i < l.length; i++) { // このコードを出力します" , " System.out.println(l[i]);" , " }" , " }" , "}" , " }" , } ; for ( int i = 0 ; i < 4 ; i ++ ) { // 開始コードを出力しますSystem.out.println ( l [ i ] ) ; } for ( int i = 0 ; i < l.length ; i ++ ) { //文字列配列を出力しますSystem.out.printf ( " %s%c%s%c,%n" , l [ 4 ] , q , l [ i ] , q ); } for ( int i = 5 ; i < l.length ; i ++ ) { //このコードを出力しますSystem .アウト.println ( l [私] ); } } }ソース コードには、それ自体の文字列配列が含まれており、引用符で囲まれて 1 回ずつ、2 回出力されます。
このコードはc2.comのオリジナル記事から改変したもので、作者のJason WilsonがJavaコメントなしのQuineの最小限のバージョンとして投稿しました。[ 4 ]
Java 15でテキストブロック機能が導入されたことで[ 5 ] 、より読みやすくシンプルなバージョンが可能になりました。[ 6 ]
パブリッククラスQuine {パブリックスタティックvoid main ( String [] args ) { String textBlockQuotes = new String ( new char [] { '"' , '"' , '"' }); char newLine = 10 ; String source = """パブリック クラス Quine { パブリック スタティック void main(String[] args) { String textBlockQuotes = new String(new char[]{'"', '"', '"'}); char newLine = 10; String source = "%s%s%s%s"; System.out.print ( source.formatted ( textBlockQuotes , newLine, source, textBlockQuotes)); } } """ ; System.out.print ( source.formatted ( textBlockQuotes , newLine , source , textBlockQuotes )) ; } }これはJava 25 コード の最新バージョンの例です。
void main () {文字列s = """ void main() { 文字列 s = %c%c%c %s%c%c%c; IO.print(s.formatted(34, 34, 34, s, 34 , 34, 34)); } """ ; IO .print ( s.formatted ( 34 , 34 , 34 , s , 34 , 34 , 34 ) ); }上記のコードは、その内容を「 App.java 」などの.java拡張子を持つ任意のファイルにドロップし、「 java App.java 」で実行するだけで実行できます。
同じ考え方が次のSQLクインでも使用されています。
SELECT REPLACE ( REPLACE ( 'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine' , CHAR ( 34 ), CHAR ( 39 )), CHAR ( 36 ), 'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS Quine' ) AS Quine一部のプログラミング言語には、文字列をプログラムとして評価する機能があります。Quine はこの機能を利用できます。例えば、次のRuby のQuine は次のようになります。
eval s = "print 'eval s=';p s"Lua では次のことが可能です:
s = "print(string.format('s=%c%s%c; load(s)()',34,s,34))" ; load ( s )()Python 3.8 の場合:
exec ( s := 'print("exec(s:= %r )" %s )' )Schemeやその他のLisp言語、そしてAPLなどの対話型言語を含む多くの関数型言語では、数値は自己評価型です。TI -BASICでは、プログラムの最終行が値を返す場合、返された値は画面に表示されます。そのため、これらの言語では、1桁の数字のみで構成されるプログラムは1バイトのクイン(quine)になります。このようなコードは自己構築されないため、しばしば不正行為とみなされます。
1一部の言語、特にスクリプト言語では、空のソースファイルは言語の固定点であり、出力を生成しない有効なプログラムです。[ a ]
このような空のプログラムは、「世界最小の自己複製プログラム」として提出され、かつて国際難読化Cコードコンテストで「最悪のルール違反」賞を受賞した。[ 7 ]このプログラムは有効なC言語ではなかった(main()関数がない)ため、実際にはコンパイルされていなかったが、空のファイルを別のファイルにコピーするMakefileスクリプトが付属しており、このスクリプトはシェルスクリプトとして実行され、何も印刷されなかった。[ 8 ]cp
クインは定義上、ファイルの読み取りを含め、いかなる形式の入力も受け付けません。つまり、クインが自身のソースコードを参照することは「不正行為」とみなされます。以下のシェルスクリプトはクインではありません。
#!/bin/sh # 無効な quine。# 実行されたファイルをディスクから読み取るのは不正行為です。cat $ 0シェバンディレクティブ の動作を利用した、より短いバリエーション:
#!/bin/catその他の問題のある手法としては、コンパイラ メッセージを利用することが挙げられます。たとえば、GW-BASIC環境では、「構文エラー」と入力すると、インタープリタは「構文エラー」と応答します。
Quine コードは視覚的に出力することもできます。たとえば、構文サッカリンとともにYars' Revengeの中立ゾーンを視覚化してソース コードを難読化するために使用されます。
クワインの概念は複数レベルの再帰に拡張することができ、「ウロボロスプログラム」またはクワインリレーを生み出します。これはマルチクワインと混同しないでください。
この Java プログラムは、元の Java コードを出力する C++ プログラムのソースを出力します。
stdをインポートします。int main ( int argc , char * argv []) { char q = 34 ; std :: array < std :: string , 36 > l = { " " , "============<<<<<<<< C++ コード >>>>>>>>===============" , "import std;" "" , "int main(int argc, char* argv[]) {" , " char q = 34;" , " std::array<std::string> l = {" , " };" , " for (int i = 19; i <= l.size(); ++i) {" , " std::println( \" {} \" , l[i]);" , " }" , " for (int i = 0; i <= l.size(); ++i)" , " std::println( \" {}{}{}{}, \" , l[0], q, l[i], q);" , " }" , " for (int i = 2; i <= 17; ++i)" , " std::println( \" {} \" , l[i]);" , " }" , " return 0;" , "}" , "============<<<<<<< Java コード >>>>>>>>===============" , "public class Quine {" , " public static void main(String[] args) {" , " char q = 34;" , " String[] l = {" , " };" , " for (int i = 2; i <= 17; ++i)" , " System.out.println(l[i]);" , " }" , " for (int i = 0; i < l.length; ++i) {" , " System.out.printf( \" %s%c%s%c,%n \" , l[0], q, l[i], q);" , " }" , " for (int i = 18; i <= l.length; ++i) {" , " System.out.println(l[i]);" , " }" , " }" , "}" ,}; for ( int i = 19 ; i <= l . size (); ++ i ) std :: println ( "{}" , l [ i ]);; }for ( int i = 0 ; i <= l . size (); ++ i ) std :: println ( "{}{}{}{}," , l [ 0 ], q , l [ i ], q ); } for ( int i = 2 ; i <= 17 ; ++ i ) std :: println ( "{}" , l [ i ] );; } 0を返します; }public class Quine { public static void main ( String [] args ) { char q = 34 ; String [] l = { " " , "=============<<<<<<<< C++ コード >>>>>>>>===============" , "import std;" , "" , "int main(int argc, char* argv[]) {" , " char q = 34;" , " std::array<std::string> l = {" , " };" , " for (int i = 19; i <= l.size(); ++i) {" , " std::println(\"{}\", l[i]);" , " for (int i = 0; i <= l.size(); ++i) {" , " std::println(\"{}{}{}{},\", l[0], q, l[i], q);" , " for (int i = 2; i <= 17; ++i) {" , " std::println(\"{}\", l[i]);" , " }" , " return 0;" , "}" , "============<<<<<<< Java コード >>>>>>>>==============" , "public class Quine {" , " public static void main(String[] args) {" , " char q = 34;" , " String[] l = {" , " };" , " for (int i = 2; i <= 17; ++i)" , " System.out.println(l[i]);" , " for (int i = 0; i < l.length; ++i)" , " System.out.printf(\"%s%c%s%c,%n\", l[0], q, l[i], q);" , " for (int i = 18; i <= l.length; ++i) {" , " System.out.println(l[i]);" , " }" " }" , "}" , }; for ( int i = 2 ; i <= 17 ; ++ i ) { System . out .println ( l [ i ] ); } for ( int i = 0 ; i < l . length ; ++ i ) { System . out . printf ( "%s%c%s%c,%n" , l [0 ] , q , l [ i ] , q ); } for ( int i = 18 ; i <= l . length ; ++ i ) { System . out . println ( l [ i ] ); } } }このようなプログラムは、さまざまなサイクルの長さで作成されています。
Unlambdaの開発者であるDavid Madoreは、マルチキネについて次のように説明しています。[ 18 ]
マルチクインとは、r個の異なるプログラム(r個の異なる言語で記述されているプログラム。この条件がなければ、これら全てを単一のクインとみなすこともできます)の集合です。各プログラムは、渡されたコマンドライン引数に応じて、r個のプログラムのいずれか(自身を含む)を出力できます。(不正行為は許可されていません。コマンドライン引数は長すぎてはなりません。プログラムの全文を渡すことは不正行為とみなされます。)
2 つの言語で構成されるマルチクイン (またはバイクイン) は次のようなプログラムです。
バイキンは 2 つのプログラムのセットとして考えることができ、どちらのプログラムも、指定されたコマンド ライン引数に応じて、どちらか一方を印刷できます。
理論的には、マルチクインに含まれる言語の数に制限はありません。5つの言語からなるマルチクイン(またはペンタクイン)は、Python、Perl、C、NewLISP、F#で作成されています[ 19 ]。 また、25言語からなるマルチクインも存在します[ 20 ] 。
マルチクインと似ていますが、ポリグロットプログラムとは異なり、複数のプログラミング言語またはファイル形式の構文を組み合わせることで、それらの有効な形式で記述されたコンピュータプログラムまたはスクリプトです。ポリグロットプログラムは自己複製性を持つ必要はありませんが、実行方法の1つ以上においてクインとなることもあります。
クインやマルチクインとは異なり、ポリグロット プログラムは、クリーネの再帰定理の結果として、任意の言語セット間に存在することが保証されません。これは、ポリグロット プログラムが構文間の相互作用に依存しており、常に 1 つの構文が別の構文に埋め込まれるという証明可能な特性に依存していないためです。
放射線耐性クインとは、任意の1文字を削除しても元のプログラムが再現され、文字が欠落していない状態を維持するクインのことである。必然的に、このようなクインは通常のクインよりもはるかに複雑であり、Rubyにおける以下の例からもそれがわかる。[ 21 ]
eval = 'eval$q=%q(puts %q(10210/ #{ 1 1 if 1 == 21 } }/.i rescue##/1 1"[13,213].max_by{|s|s.size}#"##").gsub(/\d/){["= \47 eval$q=%q( #$q )# \47 ## \47",:eval,:instance_,"||=9"][eval$&]} exit)#' ##'instance_eval = 'eval$q=%q(puts %q(10210/ #{ 1 1 if 1 == 21 } }/.i rescue##/1 1"[13,213].max_by{|s|s.size}#"##").gsub(/\d/){["= \47 eval$q=%q( #$q )# \47 ## \47",:eval,:instance_,"||=9"][eval$&]} exit)#' ##'/ #{ eval eval if eval == instance_eval } }/ . i rescue ##/評価評価"[eval||=9,instance_eval||=9].max_by{|s|s.size}#" ##"リレーショナルプログラミング技術を使用すると、言語のインタープリタ(または同等のコンパイラとランタイム)をリレーショナルプログラムに変換し、固定点を解くことで、クインを自動的に生成することができます。[ 22 ]
ハッシュクインとは、独自の暗号ハッシュを含むファイルです。クインと関連がありますが、動作は異なります。クインは独自のソースコードを出力するプログラムですが、ハッシュクインは通常、画像やバイナリなどの静的ファイルで、暗号ダイジェスト(例えばSHA-256)がファイル自体の内部に埋め込まれるように構築されています。
ハッシュキーの作成には通常、ファイルに調整可能なバイト領域を与え、最終的なハッシュがファイルに埋め込まれた値と一致する値を検索します。一部のハッシュキーは画像として表示され、表示されるハッシュが画像の一部であり、画像全体も同じハッシュ値になります。
ハッシュクインは何も実行したり出力したりしません。暗号学的ハッシュ関数における固定小数点を表すものであり、厳密なプログラミング上の意味でのクインの一種ではなく、クインの概念的な類似物とみなされます。