ヒューマンフェイススタックプログラミング

皆さんの多くは、スタックプログラミングとForth言語に関する記事や書籍をインターネットで見つけたと思います。 第一に、熱意の波:どれほどシンプルで、論理的で、理解しやすく、強力なのか! そして、なぜこれらのアイデアはそれほど重要ではないのですか? フォートのような言語を実際に使用するプログラマーが少ないのはなぜですか しばらくすると、失望の波がはじまります。はい、面白いアイデアですが、ソースコードを読むのがどれほど難しいか、変数、文字列、小数の処理はどれほど怪しいのでしょうか。 バイトメカニクスのグループにとって有用な興味深いおもちゃ。



画像



多くの場合、ここで終了します。 しかし、個人的には、エレガントな連結プログラミングが他のアイデアの影に残るという考えと調和することはできませんでした。 はい、ソースコードの読み取りが困難です。 はい、ワンライン症候群。 はい、アルゴリズムを理解するたびに、あなたはあなたの想像力でプログラムを翻訳し、ソースコードを読みながらスタックを想像しなければなりません。 しかし、これらはスタック言語固有の必然的な欠点であり、それがなければスタックプログラミングはスタックされなくなるのでしょうか。 少なくともそのような欠点を取り除き、プログラマーの生活を楽にすることは本当に不可能ですか? それはあなたがすることができ、すべきであることが判明しました!



問題1:単線症候群



バロンの 『プログラミング言語入門』で「ワンラインシンドローム」というフレーズを初めて見つけました。 また、この用語は広く使用されていませんが、多くの言語を表現的に特徴づけています。



1行シンドロームは、プログラムのソースコードの自由な構造を認め、短い、場合によっては1文字のキーワードを持つ言語の特徴です。 プログラマは、可能な限り多くのキーワードを1行に「プッシュ」しようとしているため、プログラムは非常に読みにくいように見えます。 この症候群は特にAPLとその子孫、Brainfuck、そしてもちろんFortで顕著です。 投稿の冒頭の図をもう一度見て、1行あたりの平均単語数を数えます。



しかし、これは解決可能な問題です。 フォートでは、短くて意味のある英単語を愛するムーアの中毒のために、ワンライン症候群が現れました。 残念ながら、そのような言葉はすぐに終わり、ムーアは接尾辞と接頭辞(いわゆる膝上の名前空間)を好まない。 そのため、今では全世界が「@!C @ C!/ MOD CR \ S P」という精神で簡潔な象形文字を1行にまとめて楽しんでいます。 この問題は、成功する単語を選択することで簡単に解決できます。 書く理由:

: FOR-EXAMPLE OVER OVER + ROT ROT - * ;
      
      





可能であれば:

 define for-exemple (ab -- {a+b}*{ab})    over over    (ab -- abab)    +    (ab -- ab a+b=c)    rot rot    (abc -- cab)    - * end
      
      





さらに良い:

 define for-exemple [ab -- r]    [ab -> abab]    +    [abc (=a+b) -> cab]    - / end
      
      





ただし、そのようなエントリについては以下で説明します。



問題2:コードの裏返し



もう1つの課題は、管理構造全体です。 もちろん、それらはアイデアと実装の点ではエレガントですが、そのようなコードはどのように読みにくいのですか:



 : sign-test ( n -- ) dup 0 < [ drop "" ] [ zero? [ "" ] [ "" ] if ] if print ;
      
      





この例では、コードブロックは基本であり、単語の定義全体が完全に表示されています。 しかし、作成後1週間でプログラムが作成者に読めないように見えるため、少しでも複雑にすることは価値があります。



命令型プログラミングラッシュからレスキューへの最も一般的な場合:



 define sign-test [x]   [x -> xx]   0 > if     " "    else     "   "   end-ifprint-string end
      
      





たぶんそれらはそれほど強力で拡張可能ではありませんが、非常に理解可能で実用的です。 また、古き良きFortranから算術演算などを作成したい場合は、スタックの最上位にあるコードブロックメカニズムを追加要素として言語に含めることができ、どこでも使用できませんが、本当に必要で正当な場合にのみ使用できます。 幸いなことに、そのようなメカニズムは食事を要求せず、実装はそれほど複雑ではありません。



砦に関しては、彼はこれに関して別の問題を抱えています:すべて同じ言葉。 ムーアは、ENDのような同一の終了語を複雑な構造に追加したくなかったので、IF、DO、および他の語は独自の語でカバーされるべきです。 したがって、これらのIF ELSE THENがすべて表示され、経験豊富なプログラマでさえ行き止まりになります。 パーサーと単語メカニズムを本当に複雑にしたくない場合は、END-IFのような単語を入力してください。 これは、ムーアが接尾辞と接頭辞を嫌うためです。 しかし、最初の問題と同様に、この点も簡単に解決でき、スタック言語の特定の欠点ではありません。



問題3:頭の中での解釈



Fortや他の積み重ねられた言語のプログラムの多くの機能のために、それらの本質を読んで理解することは困難です。 問題は、新しい単語が入力されたコードのブロックを読むたびに、想像力でプログラムを解釈し、すべてのステップで、スタック上のどの要素がどの順序でどのように機能するかを想像する必要があることです。 言うまでもなく、これは非常に骨が折れ、場所によっては非生産的です。 しかし、最も不愉快なのは、単に歴史的に非常に不幸だった以前の機能とは対照的に、そのような解釈の必要性は、スタックプログラミングの永遠の仲間です。



もちろん、この欠点を取り除くことは不可能ですが、ソースコードを読む際にプログラマの作業を大幅に促進できる方法があります。 そして最も重要なことは、最初のステップがすでに実行されていることです。 実際、Fortプログラマーは、ソースコードを読みやすくするために特定の表記法を採用しています。



 (  --  )
      
      





このようなコメントにより、スタック上の番号、番号、順序を理解しやすくなります。 関数を呼び出す前にスタックに置く必要のある数字の数と順序、および計算の結果としてスタックに残る数字の数を理解するために、毎回想像力を浪費する必要はありません。 しかし、ここに謎があります:そのようなコメントが非常に明確で有用な場合、なぜフォートプログラマーは単語の定義の最初にのみコメントを書くのですか? ヒープの後にドロップデュップスワップ腐敗がそのような説明的なコメントを書くことを妨げる宗教は何ですか?



2番目のコード例に戻りましょう。 もちろん、これは真空の場合ですが、実際の複雑なプログラムでは、そのようなコメントが配置されます:



 define for-exemple (ab -- {a+b}/{ab})    over over    (ab -- abab)    +    (ab -- ab a+b=c)    rot rot    (abc -- cab)    - / end
      
      





これらの各スワップ、腐敗、および+の後、プログラマーは頭の中でスタック上の数字の順序をシミュレートする必要はありません。 コメントに目を通すだけです。 しかし、ここに新しい問題があります:レコード



 (ab -- abab)
      
      





そして

 over over
      
      





似ています。 最初のレコードは宣言型で作成され、2番目のレコードは必須型で作成されただけです。 つまり、プログラム内の行は常に複製されます。 同じ成功を収めて、アセンブラーの利便性について話すことができます。左側にmovとretの束、右側のコメントにa = a + 1を付けてコードを記述し、読みやすさについて話します。 はい、コメントを読んでください。 しかし、プログラミング言語を使用している場合に非常に簡単かつ明確に読めるように、それらを書くように工夫することができます。 もちろん、このような言語が便利であるということにはなりません。 ソースコードの読み取りに関しては、arbitrarily意的に悪い場合があります。



(ab-abab)以上に結合する自然な欲求があります。 実際、特定の表記法でコメントを記述する場合、コンパイラはそれらを解析し、スタックの要素を再配置するために必要な単語を手動で追加できます。 括弧内のコメントは完全に無視され、人間のみが使用できます。 角括弧で囲まれたコメントは、個人と翻訳者の両方に必要です。 翻訳者は、そのような結果に到達する方法ではなく、人が必要なものを書くコメントを分析します。 つまり、宣言スタイルでスタックを操作できます。



メインの、いわば、そのような表現の種類を考えてみましょう。

1. foo [bar]または[bar-foo]を定義します

角括弧で囲まれた新しい関数の名前の後、スタックの一番上にある引数の数を指定する必要があります。 3つの引数を必要とする関数を呼び出すときにスタックに引数が2つしかない場合、従来のFortシステムはエラーをスローします。スタックは空です。 しかし、このようなコメントを使用すると、コンパイラーはコメントを「調べ」、欠落している引数の名前を判別できます。関数fooを呼び出すとき、引数rは十分ではありません。 同意して、これは無駄のない「スタックは空です」よりもはるかに有益です。 当然、上記のすべては、ソースコードが利用可能な場合の対話モードでの作業にのみ適用されます。 コンパイル後、これらのコメントは消えます。ソースコードのデバッグと読み取りにのみ必要です。 比較のために、gforthのエラー出力:



 : add-xy ( xy -- x+y ) compiled + ; ok 4 5 add-xy . 9 ok 4 add-xy . :6: Stack underflow 4 >>>add-xy<<< . Backtrace: $7FF17383C310 + ok
      
      





2. [ab-> a]

次のタイプの表現は、純粋に説明的な表現とは単語->が異なります。これは、古典的なコメントの単語に似ています。 左側の要素の数が右側の要素の数よりも多い場合、翻訳者は結論付けます。一部の要素は破棄する必要があり、もはや必要ありません。 要素がスタックの一番上にある場合、このデザインはドロップに変換されますが、そうでない場合、トランスレーターはスタックの要素を並べ替えて、スタックの一番上にガーベッジが表示されるようにします。その後、ドロップが適用されます。 言うまでもなく、たとえばスタックの中央から2つの要素を削除する必要がある場合に、このようなレコードがプログラマーの生活をどのように楽にしてくれるか。 翻訳者にこれを行うための最善の方法を決定させてください。プログラマは必要なことだけを説明します。

3. [ab-> ba]

このような表現は、要素の再配置を意味します。単語の左右の要素の数->は同じで、要素自体は同じです。 最適な順列スキームを選択するだけです。 機械にそれをさせてください、人々は、過剰なスワップドロップ腐敗の長い鎖が彼らをst迷に導くように配置されます。

4. [ab-> aabb]

左側の要素の数は、右側の要素の数よりも少なくなります。 これは、いくつかの要素を複製する必要があることを示唆しています。 しかし、これらの要素はどのような種類で、どの順序で配置する必要があるのか​​、翻訳者に決めてもらいます。 スワップデュップロットデュップの観点から考えるのは不便です。

5. [ab-> ab]

何も変わっていない、純粋に説明的なデザイン。 以下にアプリケーション例を示します。



当然、このような宣言式では、通常のコメントを使用できます。



 dup rot + [ab -> ab (a + b)]
      
      







フォートの言葉のいくつかを新しい形で説明しましょう。

 define dup [x]    [x -> xx] end define drop [x]    [x -> ] end define swap [xy]    [xy -> yx] end define over [xyz]    [xy -> xyx] end define rot [xyz]    [xyz -> yzx] end
      
      







問題4:浮動小数点数



整数が格納されている32ビットセルで構成されるスタックを想像してください。 このようなスタックは、算術演算の速度と変数アドレスを操作する便利さの両方に適しています。 しかし、3.14を4倍する必要がある場合はどうでしょうか。 小数をどこに置くか? 浮動小数点数が格納される64ビットセルのコレクションとしてスタックを編成し、整数を小数部がゼロの小数として格納する場合、算術演算の速度などの利点はすぐに失われます。



浮動小数点数用の2番目のスタックを紹介します。 その中のセルは、たとえば64ビットになりますが、より少なくなります。 ポイントのない数値はすべて整数スタックの頂点に配置され、ポイントのある数値(小数部がゼロであるにもかかわらず)はフロートスタックに格納されます。 つまり、上記の数値を次のように乗算できます。



 4.0 3.14 *f
      
      





ここで、* fは小数を乗算します(+ f、-fなどに類似)。



また、新しい宣言式も導入します。

[スタック:abc->スタック:abc]

あるスタックから要素を取得し、別のスタックに配置します。

 4 5 [integer: z1 z2 -> float: z1 z2] 2 times print-float end-times
      
      





数値4.0および5.0は、分数スタックに表示されます。 逆の操作は、小数部分を「切断」します。

新しい単語を定義します。

 define integer->float [x]    [integer: x -> float: x] end define float->integer [x]    [float: x -> intger: x] end
      
      





リターンスタックでも同様です。



投稿はかなりボリュームがあり、時には物議を醸すことが判明したため、資料の重要な部分は第2部になります。 繰り返しになりますが、ディスカッションのアイデアと批判により、執筆計画が調整されます。



All Articles