1. ホーム
  2. model-view-controller

[解決済み] 差動実行の仕組みを教えてください。

2023-06-22 03:10:09

質問

Stack Overflow でこの件に関する言及をいくつか見かけましたが、Wikipedia (該当ページは削除されています) と MFC ダイナミック ダイアログのデモ を見ても、何も分かりませんでした。誰かこれを説明してくれませんか?根本的に異なる概念を学ぶのは良いことだと思います。


回答内容から なんとなくわかってきたような気がします。ただ、1 回目はソースコードを十分に注意深く見ていなかったのだと思います。現時点では、差分実行について複雑な気持ちです。一方では、ある種の作業をかなり容易にすることができます。その一方で、それを実行に移す(つまり、選択した言語で設定する)のは簡単ではありません(もっと理解できればそうなると思います)...そのためのツールボックスは一度作ればよく、あとは必要に応じて拡張すればよいのでしょうけど。本当に理解するためには、他の言語で実装してみる必要がありそうです。

どのように解決するのか?

おやおや、Brianさん、もっと早くあなたの質問を見ていればよかったですね。これはほとんど私の (良くも悪くも) 発明品なので、お役に立てるかもしれません。

挿入される。最も短い説明で 私ができる最短の説明は、もし 通常の実行が、空中にボールを投げてキャッチするようなものだとしたら 空中にボールを投げてキャッチするようなものだとすると 差動実行は、ジャグリングのようなものです。 ジャグリングのようなものです。

ウィンドファインダーの説明は私のとは違いますが、それでいいんです。この技法を理解するのは簡単ではありませんし、うまくいく説明を見つけるのに、私は20年ほどかかりました。ここでもう一度、試してみましょう。

  • それは何でしょうか。

私たちは皆、コンピュータがプログラムを通じてステップを踏み、入力データに基づいて条件分岐を行い、何かを行うという単純な考えを理解しています。(私たちが単純な構造のゴト師レス、リターンレス コードだけを扱っていると仮定します)。このコードには、一連のステートメント、基本的な構造化条件文、単純なループ、サブルーチン呼び出しが含まれます。(今のところ、値を返す関数については忘れてください)。

ここで、2 台のコンピュータが同じコードを互いに同期して実行し、ノートを比較できることを想像してください。コンピュータ 1 は入力データ A で、コンピュータ 2 は入力データ B で実行され、並んでステップバイステップで実行されます。もし、IF(test) ......のような条件文に行き着き、ENDIF.... ENDIFのような条件文のところで、テストが真かどうかで意見が分かれたら、テストが偽と答えた方がENDIFまで飛ばして、妹が追いつくまで待ちます。(このため、妹が最終的にENDIFにたどり着くことがわかるようなコード構成になっています)。

2 台のコンピュータは互いに会話できるので、メモを比較し、2 組の入力データ、および実行履歴がどのように異なるかを詳細に説明することができます。

もちろん、差分実行(DE)では、1台のコンピュータで、2台をシミュレートして行われます。

さて、入力データは1セットだけですが、時間1から時間2へどのように変化したかを見たいとします。実行中のプログラムがシリアライザー/デシリアライザーであるとします。実行しながら、現在のデータをシリアライズ(書き出し)し、過去のデータ(前回書いたもの)をデシリアライズ(読み込み)する、ということを繰り返しています。これで、前回と今回のデータの違いを簡単に確認することができます。

書き込むファイルと読み込む古いファイルを合わせてキューやFIFO(先入れ先出し)を構成しますが、これはあまり深い概念ではありませんね。

  • それは何に適しているのでしょうか。

そのルーチンは、パイプ、タンク、バルブなどの図のようなものを描くためのより大きなルーチンに組み立てることができます。私たちは、図全体を描き直さなくても、図が少しずつ更新されるような「ダイナミックな図」にしたかったのです。(例えば、棒グラフの棒を描くルーチンは、古い高さを覚えていて、少しずつ更新していくことができることに気づきました。

これはOOPのように聞こえますね。しかし、quot;make" をオブジェクトにするのではなく、図のプロシージャの実行順序の予測可能性を利用することができます。バーの高さを連続したバイトストリームに書き込むことができるのです。そして、画像を更新するために、新しいパラメータを書き込みながら古いパラメータを順次読み取るモードでプロシージャを実行し、次の更新パスの準備をすることができます。

これはばかばかしいほど明白で、プロシージャが条件を含むとすぐに壊れるように思えます。なぜなら、新しいストリームと古いストリームが同期しなくなるからです。しかし、条件付きテストのブール値もシリアライズすれば、次のことが可能であることに気づきました。 同期を取り戻す . 自分自身を納得させ、そして証明するのにしばらく時間がかかりました。 常に が機能することを納得し、証明するのに時間がかかりました。

その結果、ユーザーはこれらの「動的なシンボル」を設計し、それらをより大きな図に組み入れることができます。

当時は、ビジュアル オブジェクト間の干渉を心配して、1 つを消しても他のオブジェクトが損傷しないようにする必要がありました。しかし、現在では、Windows のコントロールでこの技法を使用しており、レンダリングの問題は Windows に任せています。

では、何を実現するのでしょうか。コントロールを描画するプロシージャを書くことによってダイアログを構築することができ、コントロール オブジェクトを実際に記憶したり、インクリメンタルに更新したり、状況に応じて表示/削除/移動させたりすることを心配する必要がありません。その結果、ダイアログのソースコードは桁違いに小さくシンプルになり、ダイナミックレイアウトやコントロールの数の変更、コントロールのアレイやグリッドの作成なども簡単にできるようになりました。さらに、エディットフィールドのようなコントロールは、編集中のアプリケーションデータに些細にバインドすることができ、常に証明可能に正しく、そのイベントを処理する必要もありません。アプリケーションの文字列変数の編集フィールドを入れるのは1行の編集です。

  • なぜ理解しにくいのでしょうか。

私が最も説明しにくいと感じたのは、ソフトウェアについて異なる考え方をする必要があるということです。プログラマーは、ソフトウェアのオブジェクト アクション ビューに固く縛られ、何がオブジェクトで、何がクラスで、どのように表示を構築し、どのようにイベントを処理するかを知りたがるので、それを吹き飛ばすには桜の木が必要なほどです。私が伝えようとしているのは、本当に重要なのは 何を言う必要があるのか? DSL(ドメイン特化型言語)を作るときに、「変数Aをここに、変数Bをここに、変数Cをここに編集したい」と伝えるだけで、魔法のように処理してくれると想像してください。例えば、Win32には、ダイアログを定義するためのリソース言語があります。これは完全に良いDSLなのですが、十分に行き届いていない点があります。メインの手続き言語の中で生きているわけでもなく、イベントを処理してくれるわけでもなく、ループや条件、サブルーチンを含んでいるわけでもないのです。しかし、それは良い意味で、Dynamic Dialogsはその仕事を完成させようとしています。

つまり、異なる思考様式は プログラムを書くために、まず適切なDSLを見つけ(あるいは発明し)、できるだけ多くのプログラムをそのDSLでコーディングします。ということです。 それ に任せて、実装のためだけに存在するすべてのオブジェクトとアクションを処理します。

差分実行を本当に理解して使いたいのであれば、つまづくような厄介な問題が2つほどあります。私はかつて、それをコード化するために Lisp マクロでコーディングしたことがありますが、通常の言語では、落とし穴を避けるためにプログラマーを訓練する必要があります。

長文になってしまい申し訳ありません。もし私が意味をなしていなかったら、ご指摘いただけるとありがたいですし、修正してみます。

追加しました。

Java Swing には、TextInputDemo というサンプルプログラムがあります。これは静的なダイアログで、270行かかります(50の状態のリストを除く)。ダイナミックダイアログでは(MFCでは)約60行です。

#define NSTATE (sizeof(states)/sizeof(states[0]))
CString sStreet;
CString sCity;
int iState;
CString sZip;
CString sWholeAddress;

void SetAddress(){
    CString sTemp = states[iState];
    int len = sTemp.GetLength();
    sWholeAddress.Format("%s\r\n%s %s %s", sStreet, sCity, sTemp.Mid(len-3, 2), sZip);
}

void ClearAddress(){
    sWholeAddress = sStreet = sCity = sZip = "";
}

void CDDDemoDlg::deContentsTextInputDemo(){
    int gy0 = P(gy);
    P(www = Width()*2/3);
    deStartHorizontal();
    deStatic(100, 20, "Street Address:");
    deEdit(www - 100, 20, &sStreet);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "City:");
    deEdit(www - 100, 20, &sCity);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "State:");
    deStatic(www - 100 - 20 - 20, 20, states[iState]);
    if (deButton(20, 20, "<")){
        iState = (iState+NSTATE - 1) % NSTATE;
        DD_THROW;
    }
    if (deButton(20, 20, ">")){
        iState = (iState+NSTATE + 1) % NSTATE;
        DD_THROW;
    }
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "Zip:");
    deEdit(www - 100, 20, &sZip);
    deEndHorizontal(20);
    deStartHorizontal();
    P(gx += 100);
    if (deButton((www-100)/2, 20, "Set Address")){
        SetAddress();
        DD_THROW;
    }
    if (deButton((www-100)/2, 20, "Clear Address")){
        ClearAddress();
        DD_THROW;
    }
    deEndHorizontal(20);
    P((gx = www, gy = gy0));
    deStatic(P(Width() - gx), 20*5, (sWholeAddress != "" ? sWholeAddress : "No address set."));
}

を追加しました。

約40行のコードで病院の患者の配列を編集するサンプルコードです。1~6行目でデータベース("database")を定義しています。10-23行目でUI全体の内容を定義しています。30-48行目では、1人の患者の記録を編集するためのコントロールを定義しています。このプログラムの形式は、あたかも表示を一度作成すればよいかのように、時間的なイベントをほとんど意識していないことに注意してほしい。そして、被験者の追加や削除、その他の構造的な変更が行われた場合、DEが代わりに増分更新を行うことを除いて、ゼロから再作成されるかのように、単に再実行されるだけである。この利点は、プログラマーがUIのインクリメンタルな更新を行うために注意を払ったりコードを書いたりする必要がなく、正しい更新が保証されることです。この再実行がパフォーマンスの問題になると思われるかもしれませんが、変更する必要のないコントロールの更新は数十ナノ秒のオーダーで行われるため、そうではありません。

1  class Patient {public:
2    String name;
3    double age;
4    bool smoker; // smoker only relevant if age >= 50
5  };
6  vector< Patient* > patients;

10 void deContents(){ int i;
11   // First, have a label
12   deLabel(200, 20, “Patient name, age, smoker:”);
13   // For each patient, have a row of controls
14   FOR(i=0, i<patients.Count(), i++)
15     deEditOnePatient( P( patients[i] ) );
16   END
17   // Have a button to add a patient
18   if (deButton(50, 20, “Add”)){
19     // When the button is clicked add the patient
20     patients.Add(new Patient);
21     DD_THROW;
22   }
23 }

30 void deEditOnePatient(Patient* p){
31   // Determine field widths
32   int w = (Width()-50)/3;
33   // Controls are laid out horizontally
34   deStartHorizontal();
35     // Have a button to remove this patient
36     if (deButton(50, 20, “Remove”)){
37       patients.Remove(p);
37       DD_THROW;
39     }
40     // Edit fields for name and age
41     deEdit(w, 20, P(&p->name));
42     deEdit(w, 20, P(&p->age));
43     // If age >= 50 have a checkbox for smoker boolean
44     IF(p->age >= 50)
45       deCheckBox(w, 20, “Smoker?”, P(&p->smoker));
46     END
47   deEndHorizontal(20);
48 }

追記:ブライアンが良い質問をしたので、その回答はここの本文に属すると思いました。

Mike: "if (deButton(50, 20, "Add")){" statement が実際に何をしているのかがよくわかりません。deButton関数は何をするのでしょうか?また、FOR/ENDループはマクロか何かを使っているのでしょうか?- Brianです。

@Brian: そうです、FOR/ENDとIF文はマクロです。SourceForge プロジェクトには完全な実装があります。 deButton はボタン コントロールを維持します。ユーザーの入力操作が行われると、コードは "control event" モードで実行され、deButton は押されたことを検出し、TRUE を返すことで押されたことを意味するようになります。このように、"if(deButton(...)){...アクションコード ...}は、クロージャを作ったりイベントハンドラを書かなくても、ボタンにアクションコードを付けることができる方法なのです。DD_THROWは、アクションが実行されたときにパスを終了させる方法です。アクションはアプリケーションデータを変更する可能性があるので、ルーチンを通して"control event"のパスを続けるのは無効だからです。イベントハンドラを書くことと比較すると、イベントハンドラを書く手間が省け、また、いくつでもコントロールを持つことができます。

追記:すみません、quot;tains"という言葉の意味を説明すべきでした。プロシージャが最初に実行されたとき(SHOWモード)、deButtonはボタンコントロールを作成し、そのIDをFIFOに記憶させます。その後(UPDATEモード)、deButtonはFIFOからidを取得し、必要なら修正し、FIFOに戻す。ERASEモードでは、FIFOからidを読み出し、破棄し、元に戻さないので、ガベージコレクションが行われます。つまり、deButton 呼び出しはコントロールのライフタイム全体を管理し、アプリケーション データと一致するように維持します。

4番目のモードはEVENT(またはCONTROL)です。ユーザーが文字を入力したり、ボタンをクリックすると、そのイベントをキャッチして記録し、deContentsプロシージャをEVENTモードで実行します。 deButtonはFIFOからそのボタンコントロールのIDを取得し、これがクリックされたコントロールであるかどうかを尋ねます。クリックされた場合は、TRUEを返すので、アクションコードを実行することができます。そうでない場合は、FALSE を返します。一方 deEdit(..., &myStringVar) は、イベントが自分宛のものかどうかを検出し、もしそうならそれをエディットコントロールに渡し、エディットコントロールの内容をmyStringVarにコピーします。これと通常のUPDATE処理の間で、myStringVarは常にエディットコントロールの内容と等しくなります。これが、バインディングの仕組みです。スクロールバー、リストボックス、コンボボックスなど、アプリケーションのデータを編集できるあらゆる種類のコントロールに同じ考え方が当てはまります。

私のWikipediaの編集へのリンクはこちらです。 http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article