ジー、ブライアン、もっと早くあなたの質問を見ていたらよかったのに。それはほとんど私の「発明」であるため(良くも悪くも)、私は助けることができるかもしれません。
挿入:私ができる最短の説明は、通常の実行がボールを空中に投げてキャッチするようなものである場合、差動実行はジャグリングのようなものであるということです。
@windfinderの説明は私のものとは異なり、それで問題ありません。このテクニックは頭を包み込むのは簡単ではなく、うまくいく説明を見つけるのに約20年(オフとオン)かかりました。ここで別のショットを与えましょう:
私たちは皆、コンピューターがプログラムをステップスルーし、入力データに基づいて条件分岐を取り、物事を行うという単純な考えを理解しています。(単純な構造化goto-less、return-lessコードのみを扱っていると仮定します。)そのコードには、ステートメントのシーケンス、基本的な構造化条件、単純なループ、およびサブルーチン呼び出しが含まれています。(今のところ、値を返す関数については忘れてください。)
ここで、2台のコンピューターが同じコードを互いにロックステップで実行し、メモを比較できると想像してみてください。コンピューター1は入力データAで実行され、コンピューター2は入力データBで実行されます。これらは段階的に並んで実行されます。IF(test).... ENDIFのような条件文に到達し、テストが真であるかどうかについて意見の相違がある場合、偽の場合にテストを言う人はENDIFにスキップし、待機します。追いつくためにその姉妹。(これがコードが構造化されている理由であり、姉妹が最終的にENDIFに到達することがわかっています。)
2台のコンピューターは相互に通信できるため、メモを比較し、2セットの入力データと実行履歴がどのように異なるかを詳細に説明できます。
もちろん、差分実行(DE)では、1台のコンピューターで実行され、2台のコンピューターをシミュレートします。
ここで、入力データのセットが1つしかないが、それが時間1から時間2にどのように変化したかを確認したいとします。実行しているプログラムがシリアライザー/デシリアライザーであるとします。実行すると、現在のデータをシリアル化(書き込み)し、過去のデータ(最後にこれを行ったときに書き込まれた)を逆シリアル化(読み取り)します。これで、前回のデータと今回のデータの違いを簡単に確認できます。
書き込みを行っているファイルと読み取りを行っている古いファイルを合わせて、キューまたはFIFO(先入れ先出し)を構成しますが、これはそれほど深い概念ではありません。
グラフィックプロジェクトに取り組んでいるときに思いついたのは、ユーザーが「シンボル」と呼ばれる小さなディスプレイプロセッサルーチンを作成して、パイプ、タンク、バルブなどの図を描くために、より大きなルーチンに組み立てることができるということでした。ダイアグラム全体を再描画せずに段階的に更新できるという意味で、ダイアグラムを「動的」にしたかったのです。(ハードウェアは今日の基準では低速でした。)たとえば、棒グラフの棒を描画するルーチンは、古い高さを記憶し、それ自体を段階的に更新できることに気付きました。
これはOOPのようですね。ただし、「オブジェクト」を「作成」するのではなく、ダイアグラムプロシージャの実行シーケンスの予測可能性を利用できます。バーの高さをシーケンシャルバイトストリームで書き込むことができます。次に、イメージを更新するために、次の更新パスの準備ができるように、新しいパラメーターを書き込む間、古いパラメーターを順次読み取るモードでプロシージャを実行することができます。
これはばかげているように思われ、プロシージャに条件が含まれるとすぐに壊れるように見えます。これは、新しいストリームと古いストリームが同期しなくなるためです。しかし、条件付きテストのブール値もシリアル化すると、同期を取り戻すことができることに気づきました。簡単なルール(「消去モードルール」)が守られていれば、自分自身を納得させ、これが常に機能することを証明するのにしばらく時間がかかりました。
最終的な結果として、ユーザーは、表示がどれほど複雑または構造的に可変であっても、動的に更新する方法を心配することなく、これらの「動的シンボル」を設計してより大きな図に組み立てることができます。
当時、私は視覚的なオブジェクト間の干渉を心配しなければならなかったので、1つを消去しても他のオブジェクトが損傷することはありませんでした。ただし、現在はWindowsコントロールでこの手法を使用しており、レンダリングの問題はWindowsに任せています。
それで、それは何を達成しますか?これは、コントロールをペイントするプロシージャを作成することでダイアログを作成できることを意味し、実際にコントロールオブジェクトを記憶したり、段階的に更新したり、条件に応じて表示/非表示/移動したりすることを心配する必要はありません。その結果、ダイアログのソースコードは、約1桁小さく、単純になります。動的なレイアウト、コントロールの数の変更、コントロールの配列やグリッドの設定などは簡単です。さらに、編集フィールドなどのコントロールは、編集中のアプリケーションデータに簡単にバインドでき、常に正しいことが証明され、そのイベントを処理する必要はありません。アプリケーション文字列変数の編集フィールドに入力するのは、1行の編集です。
私が説明するのが最も難しいと思ったのは、ソフトウェアについて別の考え方をする必要があるということです。プログラマーはソフトウェアのオブジェクトアクションビューにしっかりと固執しているので、オブジェクトとは何か、クラスとは何か、ディスプレイを「構築」する方法、イベントを処理する方法を知りたいので、チェリーが必要です。それからそれらを爆破する爆弾。私が伝えようとしているのは、本当に重要なのはあなたが何を言う必要があるかということです。ドメイン固有言語(DSL)を構築していると想像してみてください。ここで、「ここで変数A、そこに変数B、そこに変数Cを編集したい」と言うだけで、魔法のように処理されます。 。たとえば、Win32には、ダイアログを定義するためのこの「リソース言語」があります。それが十分に進んでいないことを除いて、それは完全に良いDSLです。メインの手続き型言語に「存在」したり、イベントを処理したり、ループ/条件/サブルーチンを含んだりすることはありません。しかし、それは良い意味であり、DynamicDialogsは仕事を終わらせようとします。
したがって、異なる考え方は次のとおりです。プログラムを作成するには、まず適切なDSLを見つけて(または発明して)、その中にできるだけ多くのプログラムをコーディングします。ましょう、それが唯一の実装のために存在するすべてのオブジェクトとアクションを扱います。
差分実行を本当に理解して使用したい場合は、つまずく可能性のあるトリッキーな問題がいくつかあります。私はかつて、これらのトリッキーなビットを処理できる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行目は「データベース」を定義しています。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”)){」ステートメントが実際に何をしているのかわかりません。deButton関数は何をしますか?また、FOR / ENDループは、ある種のマクロなどを使用していますか?–ブライアン。
@Brian:はい、FOR / ENDおよびIFステートメントはマクロです。SourceForgeプロジェクトには完全な実装があります。deButtonはボタンコントロールを維持します。ユーザー入力アクションが発生すると、コードは「制御イベント」モードで実行されます。このモードでは、deButtonが押されたことを検出し、TRUEを返すことで押されたことを示します。したがって、「if(deButton(...)){...アクションコード...}は、クロージャを作成したり、イベントハンドラを記述したりすることなく、アクションコードをボタンにアタッチする方法です。DD_THROWはアクションがアプリケーションデータを変更した可能性があるため、アクションが実行されたときにパスを終了する方法。したがって、ルーチンを介して「制御イベント」パスを続行することは無効です。これをイベントハンドラーの作成と比較すると、それらを作成する手間が省けます。そしてそれはあなたがいくつものコントロールを持つことを可能にします。
追加:申し訳ありませんが、「維持する」という言葉の意味を説明する必要があります。プロシージャが最初に実行されると(SHOWモードで)、deButtonはボタンコントロールを作成し、そのIDをFIFOに記憶します。後続のパス(UPDATEモード)で、deButtonはFIFOからIDを取得し、必要に応じて変更して、FIFOに戻します。ERASEモードでは、FIFOから読み取り、破棄し、元に戻さないため、「ガベージコレクション」が行われます。したがって、deButton呼び出しは、コントロールの存続期間全体を管理し、アプリケーションデータとの一致を維持します。これが、コントロールを「維持する」と私が言う理由です。
4番目のモードはEVENT(またはCONTROL)です。ユーザーが文字を入力するかボタンをクリックすると、そのイベントがキャッチされて記録され、次にdeContentsプロシージャがEVENTモードで実行されます。deButtonはFIFOからボタンコントロールのIDを取得し、これがクリックされたコントロールであるかどうかを尋ねます。そうであった場合は、TRUEを返すため、アクションコードを実行できます。そうでない場合は、FALSEを返します。一方、deEdit(..., &myStringVar)
イベントがそのイベントを対象としていたかどうかを検出し、その場合はそれを編集コントロールに渡し、編集コントロールの内容をmyStringVarにコピーします。これと通常のUPDATE処理の間では、myStringVarは常に編集コントロールの内容と等しくなります。それが「バインディング」が行われる方法です。同じ考え方が、スクロールバー、リストボックス、コンボボックス、アプリケーションデータを編集できるあらゆる種類のコントロールにも当てはまります。
これが私のウィキペディア編集へのリンクです:http://en.wikipedia.org/wiki/User:MikeDunlavey / Difex_Article