機能性錆:牛肉の調理

画像







BrainfuckのようなCow言語を目にしました。 そして、私は彼のために通訳を書くことにしました。 また、Rustはマルチパラダイム言語であるため、プログラムの記述スタイルは機能的です。 何が起こったかを知るために-猫の下でお願いします。







チュートリアル、コメントの質問、機能の欠如についての貴重なコメントの形でストーリーをリードしていきます-そこにも修正します! それまでの間、行きましょう。







予備設計



仮想ステータス マシンは不変の変数state



保存されます。 すべての変更は、次のセマンティクスを持つ関数を使用して行われます。







 fn change(previousState: CowVM) -> CowVM // newState
      
      





ElmReduxのように見えますが、私自身は知らないかもしれません。 そうじゃない?







実際、このタスクを機能的なアプローチに適したものにするデータを保存する方法が非常に明確であるのは、まさに最初から正確です。







実際、Cow仮想マシンとは何ですか?









タスクの作業の最初の段階で、プログラムの作業のどの段階でもすべてのデータがそのように保存されると確信しています。 追加のフィールドビュー表示されず、顧客はビジネスロジックの変更を2、3回投げることはありません...プログラマーの夢!







免疫をどのように維持しますか?



実際、免疫は一方向でしか維持できません-プログラムの状態で何かを変更する必要があるたびに、 完全に新しい状態を再作成し、保存して、古い状態をスコープ外と呼ばれる埋め立て地に捨てますRust言語の以下の魔法の機能は、これに役立ちます。









モデル



モデルは同じ構造で、プログラムの1つになります。同じCOW言語の仮想マシンです。







 #[derive(Debug, Default, Clone)] pub struct CowVM { pub program: Vec<u32>, pub memory: Vec<i32>, pub program_position: usize, pub memory_position: usize, pub register: Option<i32> }
      
      





デバッグ、デフォルト、クローンなど、おいしいものを構造に追加します。 これは何であり、なぜですか- 私の前の記事で読むことができます。







減速機



ここでは、一般に、複雑なこともありません。 予備設計の段階で定義されたセマンティック禅に続いて、言語の各オペコードに対して個別のコマンドをリベットします。これは仮想マシンを取得し、すでに変更された新しいマシンを作成します。







たとえば、非常に重要なオペコードmOoの関数を考えてみましょう -コマンドは1つのポインターをメモリー位置に戻します:







 pub fn do_mOo(state: CowVM) ->CowVM { CowVM{ memory_position: state.memory_position-1, program_position: state.program_position+1, ..state } }
      
      





関数全体が行うことは2つだけです。メモリセル1へのポインタを後方に移動し、コマンドセル1へのポインタを前方に移動します。 すべての関数がプログラムセルへのポインタを前方に移動するわけではないことに注意してください。したがって、この機能を別の関数として割り当てないことにしました。 これは少しスペースを取ります-半分の行、そして関数呼び出しはほぼ同じ場所をとるので、とにかく...







さて、さて、もっと面白いことに移りましょう。 覚えておいて、私たちのレジスターはあまり普通ではないと言ったのですか? それだけです-RustにNULL値を格納するための最良の(そしておそらく唯一の適切な)方法はOption型です。 ところで、ところで、関数型プログラミングの地獄からまっすぐ。 おそらくそれが何であるかを深く掘り下げることはありません。そのようなアプローチは、そもそも言語自体によって課せられるものであり、2番目はnilnullなどの言語を持つすべての言語とは根本的に異なります。 このような言語は通常、 JavaC#PythonRubyGoなどの古典的なOOP言語と呼ばれます。一般的に継続する意味はありません。 この状態に慣れるだけです。







羊に戻りましょう。 レジスタが空であるか、空ではない可能性があるため、 Optionと同様に作業する必要があります。 そして、レデューサーのソースコードは次のとおりです。







 pub fn do_MMM(state: CowVM) -> CowVM { match state.register { Some(value) => { let mut memory = state.memory; memory[state.memory_position] = value; CowVM{ register: None, program_position: state.program_position+1, memory: memory, ..state } }, None => { CowVM{ register: Some(state.memory[state.memory_position]), program_position: state.program_position+1, ..state } }, } }
      
      





最後にこれらの4つの閉じ括弧を参照してください? 彼らは怖いです。 多くの関数型言語が括弧を使用しないのも不思議ではありません。 Brrr ...







仮想マシンのメモリ内の値を変更するための非常にエレガントな方法ではないことに注意してください。 これ以上良いものはありませんが、コメントで教えてもらえますか?







正直に言うと、「純粋に機能的な」言語には配列はありません。 リストまたは辞書があります。 リスト内の要素を置き換えるには、 O(N) 、ディクショナリ-O(logN) 、ここでは少なくともO(1)が必要です。 そしてそれは喜ぶ。 そして、フォームのメモリ:







 {"0": 0, "1": 4, .... "255": 0}
      
      





私は震えます。 それで、それがそうであるようにしてください。







残りのチームは類推によって行います-ソースでそれらを見ることができます。







プログラムのメインサイクル



ここではすべてが簡単です:









機能的なアプローチがあるため、すべてをサイクルなしで行う必要があります:再帰的に。 やってみましょう。







メインの再帰関数を定義する-execute







 fn execute(state: CowVM) -> CowVM { new_state = match state.program[state.program_position] { 0 => commands::do_moo(state), 1 => commands::do_mOo(state), 2 => commands::do_moO(state), 3 => commands::do_mOO(state), 4 => commands::do_Moo(state), 5 => commands::do_MOo(state), 6 => commands::do_MoO(state), 7 => commands::do_MOO(state), 8 => commands::do_OOO(state), 9 => commands::do_MMM(state), 10 => commands::do_OOM(state), 11 => commands::do_oom(state), _ => state, } execute(new_state) }
      
      





ロジックは単純です。新しいコマンドを監視して実行し、最初に繰り返します。 そして最後まで続きます。







以上です。 COW言語通訳-準備完了!







現在のメインプログラムサイクル



あなたは私に尋ねます- 「それは冗談ですか?」 「マルチパラダイム」(ハハ!) Rust言語にはTail Call Optimizationはないことが判明したとき、私は同じ質問をしました。 (それは何ですか- ここで読んでください 。)







この面白い小さなものがなければ、 stackoverflowサイトがそのように命名されている理由をすぐに自分で見つけることができます。







さて、あなたはサイクルをやり直さなければなりません。







これを行うには、 実行関数から再帰をスローします。







 fn execute(state: CowVM) -> CowVM { match state.program[state.program_position] { 0 => commands::do_moo(state), 1 => commands::do_mOo(state), 2 => commands::do_moO(state), 3 => commands::do_mOO(state), 4 => commands::do_Moo(state), 5 => commands::do_MOo(state), 6 => commands::do_MoO(state), 7 => commands::do_MOO(state), 8 => commands::do_OOO(state), 9 => commands::do_MMM(state), 10 => commands::do_OOM(state), 11 => commands::do_oom(state), _ => state, } }
      
      





そして、 メイン関数で直接ループを開始します。







 fn main() { let mut state = init_vm(); loop { if state.program_position == state.program.len() { break; } state = execute(state); } }
      
      





世界の関数型プログラミングの痛みを感じますか? この言語は、ネイティブの再帰の美しさを忘れさせるだけでなく、可変変数を作成しました!!!







実際、残念ながら、そう書くとうまくいきません。







 fn main() { let state = init_vm(); loop { if state.program_position == state.program.len() { break; } let state = execute(state); } }
      
      





夕暮れに隠されている理由のため...実際には、 loop



内で作成された変数は、スコープを離れると消えます(この場合、次の行)。







Mu-Mu-Muソースを読む



しかし、 RustIOを操作するのに機能的なものはありません。 絶対に。 したがって、この部分はこの記事の範囲外であり、このインタープリターのソースコードで見つけることができます。







おわりに



主観的な感覚によると、 は年齢なしで錆びることに成功しました。 そして、その中のOOPはどういうわけかOOPではなく、FPは正確にはFPではありません。 しかし-「マルチパラダイム」。 ただし、これらのパラダイムの接点で、何かすごいものが得られるかもしれません! それと、 Rustのピストルが期待されています。







ただし、機能的アプローチには明らかな利点があります。 プログラム全体を書いたので、次のことが可能になりました。









あとがき



それを読んだすべての人に感謝します。 Rustのさまざまなプログラミングパラダイムについて議論できる記事にコメントしてください。 コメント、提案-すべて同じ。







ソースへのリンク







謝辞



私にとって特に難しい場所機能的ではないため)を開発してくれた@minizinger 、インスピレーション、サポートに感謝します。








All Articles