1つのクールなガベージからさらにクールなガベージまたは膝の上に関数型言語を作成する方法

「小石を水に投げ入れ、それらが形成する円を見てください。 そうでなければ、そのようなスローは空の楽しみになります。

K.プルトコフ



かつては、インターネットサーフィンを使って仕事目的と雇用主のお金を無制限に費やしながら、 いつでも言語の説明に出会い、しばらくの間、私は魅了されました。 この言語は、とてつもないシンプルさで際立っています。 その原則は次のとおりです。



1)プログラムコードの行はいつか必ず実行されますが、実行順序は、記述された順序とはまったく関係ありません。

2)変数? 実行順序を制御することさえできません。変数は必要ありません。

3)データ構造? はい、冗談です!



つまり、プログラムは実行用の行のセット(プール)として解釈され、インタープリターはそこから行をランダムに選択し、コマンドを実行してプールからスローします。 そして、プールに何も残らなくなるまで。 私は、この狂気の著者がその概念をほぼ立てたことを認めなければなりません。 ほとんどの場合、実行可能プールに行を追加する機能を使用して変数を追加できるように、プログラム内で実行順序を整理することは可能です。



そのため、この言語には次の構成要素があります。



コード文字列を表示



line-number statement;







式は、コンマで区切られたコマンドで構成されます。 それらの中には:

行番号



line-number#number-of-times-to-add/remove







たとえば、1行目のコマンド



1 4,5#3,-6;







実行プールに番号4の1行、番号5の3行を追加し、プールから行6を削除します。



無限ループのプログラムは簡潔に見えるため、見事です。



1 1;







インタープリターは、プールから最初の行を選択し(他にはない)、プールに追加します。 ちなみに、後で見るように、非常に便利なデザインです。



画面出力



括弧内の内容を画面に印刷する印刷コマンドだけです。



これらのコマンドに加えて、次の設計があります。



延期する

括弧内の式が「true」を返す場合、コマンドは実行されず、行はプールに戻されます。 例えば



1 defer (2) 3;







プールに行2が存在する限り、プールに行3を追加するコマンドは実行されません。



再び

括弧内の式が「true」を返す場合、コマンドは実行されますが、行はプールに戻ります。 例:



1 again (2) 3;







行3は常にプールに追加されますが、プールに行2がある場合、その後、行1はプールから削除されません。



式(2)は(N(2)> 0)と同等であることに注意してください。ここで、N()はプール内の数値の行数を返す関数です。



忘れる

括弧内の式が「true」を返す場合、コマンドは実行されず、行はプールから削除されます。 例:



1 forget(2) 3;







言語の作成者はこの表現を言語から除外しましたが、以下に説明するように、私はそれを返さなければなりませんでした。



繰り返しますが、defer&forgetは組み合わせることができますが、各修飾子は1行に1回しか表示されません。



実際にはすべて、言語全体。 言語の説明があるサイトには、2つのプログラムが含まれています。



99本のビール



 1 defer (4 || N(1)<N(2) && N(2)<N(3)) print(N(1)+" bottles of beer on the wall, "+N(1)+" bottles of beer,"); 2 defer (4 || N(1)==N(2)) print("Take one down and pass it around,"); 3 defer (4 || N(2)==N(3)) print(N(1)+" bottles of beer on the wall."); 4 1#98,2#98,3#98;
      
      







およびフィボナッチ数(1番目から100番目の要素までのフィボナッチ数列を計算します...まあ...待つ場合):



 1 again (1) defer (3 || N(1)<=N(2) || N(7)>99) 2#N(1),3,7; 2 again (2) defer (3 || N(2)<=N(1) || N(7)>99) 1#N(2),3,7; 3 defer (5) print(N(1)+N(2)); 4 defer (5) print("1"); 5 4,-3,7; 6 defer (4) 3; 7 7; 8 defer (N(7)<100) -1#N(1),-2#N(2),-7#100,-3; 9 defer (3 || 6) 1,3;
      
      







最初のプログラムが純粋ないたずらであれば、2番目のプログラムは意味のあることを行い、これは驚くべきことです。 定義上、そのようないたずらから価値のあるものは出てこないようです。 しかし、ここにあります。



そして、私はこのすべてで何かをしたかったです。 結果はやや予想外でしたが、最初に最初にしたことです。



これらのプログラムでは不便であることはすぐにわかります。フィボナッチ数列を100分の1までではなく、たとえば20の要素まで計算するには、プログラムは4箇所でテキストを変更する必要があります。 これはなんとなく面倒です。 プログラムが入力パラメーターを受け入れることができるようにします。 たとえば、次のように:



 1 again (1) defer (3 || N(1)<=N(2) || N(7)>( @1 - 1 )) 2#N(1),3,7; 2 again (2) defer (3 || N(2)<=N(1) || N(7)>( @1 - 1 )) 1#N(2),3,7; 3 defer (5) print(N(1)+N(2)); 4 defer (5) print("1"); 5 4,-3,7; 6 defer (4) 3; 7 7; 8 defer (N(7)<@1) -1#N(1),-2#N(2),-7#100,-3; 9 defer (3 || 6) 1,3;
      
      







プログラムのテキストをファイルfib.srcに保存します。 プログラム呼び出しは次のようになります。

java -cp Whenever.jar Whenever fib.src 20







もういい 成功を統合するために、2つの自然数の最大公約数を見つけるプログラムを作成しました。



 1 defer(2) again(N(3) - (N(3)/N(4))*N(4) != 0) 6#N(3),-3#N(3),3#N(4),-4#N(4),4#(N(6) - (N(6)/N(3))*N(3)),-6#N(6); 2 3#( @1 - 1 ),4#( @2 - 1 ),-6; 3 3; 4 4; 5 defer(1) print("NOD of " + @1 + " and " + @2 + " is " + N(4)),-3#N(3),-4#N(4); 6 6;
      
      







プログラムを呼び出すときは、2つの番号を指定する必要があります。 このプログラムを分析します。



3行目と4行目は、2つの数値を格納するメモリセルとして使用されます。 より正確には、対応する行のプール内のエントリの数が変数として使用されます。 行6は、同じ逆の方法で中間変数として使用されます(言語は別のものを残しません)。

行2はプロセス全体を開始します。 N行3とM行4をプールに追加し、6行目をプールから削除すると、プールから完全に削除されます。

行1が実際にすべての作業を行います。 行1の実行は、プールに行2ができるまで延期されます(遅延(2))。 さらに、GCDが見つかるまで(再び)、文字列はプールからスローされません。 括弧内の式は、行3の数を行4の数で除算した余りを示します(整数演算が使用されます)。 実際、1行目のアクションは左から右に実行されます。

  1. 行プール3のこのステップと同じ数の行6がプールに追加されます。
  2. 行3はすべてプールから削除されます
  3. 与えられた瞬間に4行あるので、多くの行が追加されます(これら2つのコマンドは1つに折りたたむことができます-3#(N(3)-N(4))
  4. 行4をすべて削除します
  5. 行4を追加します==大きい数字を小さい数字で割った余り。 繰り返しますが、これら2つのチームは1つにまとめることができます。
  6. プールからすべての行6を削除します(バイオリニストは必要ありません)


5行目は結果を画面に表示し、それ自体をクリーンアップして、プールから残っているものをすべて削除します。 その実行は、行1がプールに存在するまで遅延され、GCDが見つかるまでそこに残ります。 言語印刷の元のバージョンでは、他のコマンドと組み合わせることができないため、そこに書き込む必要があることに注意してください

 5 defer(1) print("NOD of " + @1 + " and " + @2 + " is " + N(4)); .... 7 defer(5) -3#N(3),-4#N(4);
      
      







うん、思った。 そして、プールに行を追加するときに、パラメーターを渡すことができるようにしましょう。 構文では既に括弧とコンマが使用されており、すべてを強く書き直したくはなかったので、パラメーターセパレーターには角括弧とパーセント記号%を使用しました。



微妙な点が1つあります。 プールに行を追加する場合、実際には、新しい番号を持つ別の行を作成する必要があります。既存の行の実行数は増加しません。 (実際には、「クリーンさ」のために、同じパラメーターを持つ行の実行回数を増やす必要がありますが、まだ実装していません)。



それから突然、別の次元、別の変数、つまり現在の行番号ができました。 それを使用できるようにするために、別のキーワード-selfを入力しました。



やった! 今すぐ何か役に立つことができます。 たとえば、同じフィボナッチ数列を書き換えます。



 1 forget(self>@3) print("iter: " + self + " value: " + @1 ),1[@1+@2%@1%@3];
      
      







3つのパラメーター(1、0、および計算が必要な番号)がプログラムに渡されます。 良いことには、3番目のパラメーターのみが重要であり、プログラムはそれを受け入れるように書き換えることができます。 それから、ところで、自己を取り除くことは可能ですが、プログラムは非常に美しくはありませんが、2つの行全体に大きく成長します。



注:1)忘れられた権限を復元し、2)他の多くのコマンドで印刷を実行する必要がありました(元のコマンドでは、印刷または他のコマンドしか実行できませんでした)。



そして、ここにGCDがあります:



 1 forget( @2 == 0 ) 1[@2%(@1 - (@1 / @2) * @2)],2[@2%(@1 - (@1 / @2) * @2)]; 2 forget( @2 > 0 ) print("Nod is: " + @1 );
      
      







繰り返しますが、より大きなパラメーターがどの数値に依存していないかを確認できますが、私は面倒です。



まず、番号が素数であるかどうかを確認します。

 1 defer(5) forget( @2 == 0 || @2 == 1 ) 4[@1%(@2 - 1)],2[@1%(@2 - 1)],3[@1%(@2 - 1)]; 2 defer(5) forget( @2 == 0 || @1 != (@1 / @2) * @2 || @1 == @2 || @2 == 1 ) print( "Number " + @1 + " is not prime"); 3 defer(5) forget( @2 != 1 ) print( "Number " + @1 + " is prime"); 4 defer(5) forget( @2 == 0 || @1 == (@1 / @2) * @2 ) 1[@1%@2]; 5 1[@1%@1];
      
      







ここでもう1つのトリックを使用します。パラメーターが指定されていない場合、ゼロに等しくなります。



これらは子猫と一緒のパイです。 このゴミの組み合わせの問題を解決しようとすることができるということを教えてくれます。 しかし、解決策を思い付くのは面倒です。 しかし...



All Articles