BrainfuckのようなCow言語を目にしました。 そして、私は彼のために通訳を書くことにしました。 また、Rustはマルチパラダイム言語であるため、プログラムの記述スタイルは機能的です。 何が起こったかを知るために-猫の下でお願いします。
チュートリアル、コメントの質問、機能の欠如についての貴重なコメントの形でストーリーをリードしていきます-そこにも修正します! それまでの間、行きましょう。
予備設計
仮想ステータス 牛 マシンは不変の変数state
保存されます。 すべての変更は、次のセマンティクスを持つ関数を使用して行われます。
fn change(previousState: CowVM) -> CowVM // newState
Elm 、 Reduxのように見えますが、私自身は知らないかもしれません。 そうじゃない?
実際、このタスクを機能的なアプローチに適したものにするデータを保存する方法が非常に明確であるのは、まさに最初から正確です。
実際、Cow仮想マシンとは何ですか?
- コマンドの配列は、最も一般的な整数配列です。
- メモリは2番目の同じ単純な整数配列です。
- 現在のコマンドセル-コマンド配列内のセルインデックス。
- 現在のメモリセル-メモリアレイのセルインデックス。
- レジスタはほぼ規則的な整数です。 なぜほとんど? 詳細をご覧ください!
- ???
- 利益
タスクの作業の最初の段階で、プログラムの作業のどの段階でもすべてのデータがそのように保存されると確信しています。 追加のフィールドやビューは表示されず、顧客はビジネスロジックの変更を2、3回投げることはありません...プログラマーの夢!
免疫をどのように維持しますか?
実際、免疫は一方向でしか維持できません-プログラムの状態で何かを変更する必要があるたびに、 完全に新しい状態を再作成し、保存して、古い状態をスコープ外と呼ばれる埋め立て地に捨てます 。 Rust言語の以下の魔法の機能は、これに役立ちます。
- 特性コピー 。 OOPから来た場合、 コピー特性が定義されている構造は参照型ではなく値型です。
ここでプレイできますが、要するに-代入は変数を受け取らず、コピーします。 - 魔法の構造更新構文 。 このことにより、古い構造から新しい構造にフィールドが突然コピーされます。
重要:残念ながら、 Copyトレイトが定義されていないパラメーターはコピーしません(この例ではVec <i32> )。 彼はただそれらを運んでいます。 そして、これは非常に重要です。なぜなら、あらゆる種類のエラーに巻き込まれる可能性があるからです。 しかし 、私たちの場合はそうではありません! 結局のところ、私たちは古い構造を気にしません-それからそこに転送されるものは私たちにとって重要ではありません-私たちはそれを決して使用しません。 これは景品です!!
モデル
モデルは同じ構造で、プログラムの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番目はnil 、 nullなどの言語を持つすべての言語とは根本的に異なります。 このような言語は通常、 Java 、 C# 、 Python 、 Ruby 、 Goなどの古典的な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}
私は震えます。 それで、それがそうであるようにしてください。
残りのチームは類推によって行います-ソースでそれらを見ることができます。
プログラムのメインサイクル
ここではすべてが簡単です:
- mu-mu-muソースを読み取り、
- 空のメモリとコマンドの配列を持つ新しい仮想マシンを作成し、
- プログラムが終了するまで、すべてのコマンドを順番に実行します。
機能的なアプローチがあるため、すべてをサイクルなしで行う必要があります:再帰的に。 やってみましょう。
メインの再帰関数を定義する-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ソースを読む
しかし、 RustでIOを操作するのに機能的なものはありません。 絶対に。 したがって、この部分はこの記事の範囲外であり、このインタープリターのソースコードで見つけることができます。
おわりに
主観的な感覚によると、 錆は年齢なしで錆びることに成功しました。 そして、その中のOOPはどういうわけかOOPではなく、FPは正確にはFPではありません。 しかし-「マルチパラダイム」。 ただし、これらのパラダイムの接点で、何かすごいものが得られるかもしれません! それと、 Rustのピストルが期待されています。
ただし、機能的アプローチには明らかな利点があります。 プログラム全体を書いたので、次のことが可能になりました。
- PLOの廊下に一度入ったり、単一のクラスを作成したりしないでください。
- 遅延、再割り当てに問題が生じることは決してありません。変数を操作するときに、 Rustが何を持っているかを神はまだ知っています。 はい、私たちは通常、参照も変数変数も(ほとんど)作成しませんでした。
borrow
とownership
忘れborrow
しまいました。 率直に言って、これだけではコンパイルできないものを考えずに書くのは本当に楽しいです。 - また、 ライフタイムパラメータを入力することもできませんでした。これは、実際すべてのRustの薄明かりです。 正直に言うと、私は怖いです
(x: &'a mut i32)
。これをすべて回避できてとてもうれしいです。 - 特性を実装していません。 まあまあの成果ですが、突然、特性では特性はそれほど必要ではないことがわかりました。
- これらの機能はすべて本質的に純粋であり、非常に簡単にテストできます(OOPとFIでのテストの違いは長い間知られており、グーグルで検索するのは簡単ですが)。
あとがき
それを読んだすべての人に感謝します。 Rustのさまざまなプログラミングパラダイムについて議論できる記事にコメントしてください。 コメント、提案-すべて同じ。
謝辞
私にとって特に難しい場所 ( 機能的ではないため)を開発してくれた@minizinger 、インスピレーション、サポートに感謝します。