Гей, Брайане, я би хотів, щоб я побачив ваше питання раніше. Оскільки це майже мій "винахід" (в кращу чи гіршу сторону), я міг би допомогти.
Вставлено: Найкоротше можливе пояснення, яке я можу зробити, полягає в тому, що якщо звичайне виконання подібно до того, як кидати м’яч у повітря і ловити його, то диференціальне виконання - як жонглювання.
Пояснення @ windfinder відрізняється від мого, і це нормально. За допомогою цієї техніки непросто обернути голову, і мені знадобилося 20 років (не зважаючи на це), щоб знайти пояснення, які працюють. Дозвольте мені зробити ще один постріл тут:
Ми всі розуміємо просту ідею комп’ютера, який крокує по програмі, бере умовні гілки на основі вхідних даних і робить щось. (Припустімо, ми маємо справу лише з простим структурованим кодом без гото, без повернення.) Цей код містить послідовності операторів, основні структуровані умовні умови, прості цикли та виклики підпрограм. (Наразі забудьте про функції, що повертають значення.)
А тепер уявіть, як два комп’ютери виконують один і той самий код між собою і можуть порівнювати нотатки. Комп’ютер 1 працює із вхідними даними A, а Комп’ютер 2 - із вхідними даними B. Вони виконують покроково поруч. Якщо вони приходять до умовного висловлювання типу IF (тест) .... ENDIF, і якщо у них різниця в думках щодо того, чи є тест істинним, то той, хто говорить тест, якщо false, переходить до ENDIF і чекає його сестру наздогнати. (Ось чому код структурований, тому ми знаємо, що сестра врешті-решт дістанеться до ENDIF.)
Оскільки два комп'ютери можуть спілкуватися між собою, вони можуть порівнювати нотатки та давати детальне пояснення того, як відрізняються два набори вхідних даних та історії виконання.
Звичайно, при диференціальному виконанні (DE) це робиться з одним комп'ютером, імітуючи два.
ЗАРАЗ, припустимо, у вас є лише один набір вхідних даних, але ви хочете побачити, як вони змінювались час від часу до часу 2. Припустимо, що програма, яку ви виконуєте, є серіалізатором / десериалізатором. Під час виконання ви обидва серіалізуєте (виписуєте) поточні дані та десеріалізуєте (читаєте) минулі дані (які були записані востаннє, коли ви це робили). Тепер ви можете легко зрозуміти, які відмінності існують між тим, які дані були минулого разу, і якими вони є цього разу.
Файл, до якого ви пишете, і старий файл, з якого ви читаєте, узяті разом, становлять чергу або FIFO (перший-у-першому-вихід), але це не дуже глибоке поняття.
Мені це спало на думку, коли я працював над графічним проектом, де користувач міг побудувати невеликі підпрограми процесора дисплея, що називаються "символами", які можна було б зібрати у більші процедури, щоб намалювати такі речі, як схеми труб, резервуарів, клапанів тощо. Ми хотіли, щоб діаграми були "динамічними" в тому сенсі, що вони могли поступово оновлюватись без необхідності перекроювати всю діаграму. (Апаратне забезпечення було повільним за сучасними стандартами.) Я зрозумів, що (наприклад) процедура складання стовпчика гістограми може запам'ятати її стару висоту і просто поступово оновлюватись.
Це звучить як ООП, чи не так? Однак замість того, щоб "робити" "об'єкт", я міг скористатися передбачуваністю послідовності виконання процедури діаграми. Я міг записати висоту стовпчика послідовним потоком байтів. Потім, щоб оновити зображення, я міг би просто запустити процедуру в режимі, коли вона послідовно зчитує свої старі параметри, тоді як пише нові параметри, щоб бути готовим до наступного проходження оновлення.
Це здається по-дурному очевидним і, здається, зламається, як тільки процедура містить умовне значення, оскільки тоді новий потік і старий потік вийдуть з синхронізації. Але потім мені спало на думку, що якщо вони також серіалізують логічне значення умовного тесту, вони можуть повернутися синхронізовано . Потрібен був час, щоб переконати себе, а потім довести, що це завжди спрацьовує, за умови дотримання простого правила ("правило стирання режиму").
Кінцевим результатом є те, що користувач міг спроектувати ці "динамічні символи" та зібрати їх на більші схеми, ніколи не турбуючись про те, як вони динамічно оновлюватимуться, незалежно від того, наскільки складним чи структурним змінним буде дисплей.
У ті часи мені довелося турбуватися про втручання між зоровими об’єктами, щоб стирання одного не зашкодило іншим. Однак зараз я використовую техніку з елементами керування Windows, і я дозволяю Windows піклуватися про проблеми з візуалізацією.
То чого він досягає? Це означає, що я можу побудувати діалогове вікно, написавши процедуру розфарбовування елементів керування, і мені не доведеться турбуватися про те, щоб насправді запам’ятати об’єкти керування або мати справу з поступовим їх оновленням, або змусити їх з’являтися / зникати / рухатись, як того вимагають умови. Результатом є набагато менший та простіший вихідний код діалогового вікна приблизно на порядок, і такі речі, як динамічне розміщення або зміна кількості елементів керування або наявність масивів або сіток елементів керування, є тривіальними. Крім того, такий елемент керування, як поле Редагування, може бути тривіально прив'язаний до даних програми, яку він редагує, і він завжди буде доказово правильним, і мені ніколи не доведеться мати справу з його подіями. Введення поля редагування для змінної рядка програми є однорядковим редагуванням.
Найважче мені пояснити, що це вимагає іншої думки про програмне забезпечення. Програмісти настільки міцно прив'язані до об'єктного погляду програмного забезпечення, що вони хочуть знати, що це за об'єкти, які класи, як вони "будують" дисплей і як вони обробляють події, що для цього потрібна вишня бомба, щоб вирвати їх з неї. Я намагаюся донести, що насправді важливо те, що вам потрібно сказати?Уявіть, що ви створюєте специфічну для домену мову (DSL), де все, що вам потрібно зробити, це сказати їй "я хочу відредагувати змінну A тут, змінну B там, а змінну C там", і це магічним чином подбало б про вас . Наприклад, у Win32 існує така "мова ресурсів" для визначення діалогів. Це цілком хороший DSL, за винятком того, що він заходить недостатньо далеко. Він не "живе в" основній процедурній мові, не обробляє для вас події, не містить циклів / умов / підпрограм. Але це добре, і "Динамічні діалоги" намагаються закінчити роботу.
Отже, інший спосіб мислення такий: щоб написати програму, спочатку ви знайдете (або винайдете) відповідний 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 визначають загальний зміст інтерфейсу користувача. Рядки 30-48 визначають елементи керування для редагування запису окремого пацієнта. Зверніть увагу, що форма програми майже не зауважує подій у часі, ніби все, що їй потрібно було зробити, це створити дисплей один раз. Потім, якщо об'єкти додаються або видаляються або відбуваються інші структурні зміни, вони просто виконуються повторно, ніби вони відтворюються з нуля, за винятком того, що DE викликає натомість поступове оновлення. Перевага полягає в тому, що вам, програмісту, не потрібно приділяти ніякої уваги або писати будь-який код, щоб виконувати поступові оновлення інтерфейсу, і вони гарантовано правильні. Може здатися, що це повторне виконання буде проблемою продуктивності, але це не так,
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 створює елемент керування кнопкою та запам'ятовує його ідентифікатор у FIFO. На наступних передачах (в режимі UPDATE) deButton отримує ідентифікатор з FIFO, модифікує його, якщо потрібно, і повертає в FIFO. У режимі ERASE він зчитує його з FIFO, знищує і не повертає назад, тим самим «збираючи сміття». Отже, виклик deButton управляє всім часом керування, зберігаючи його у відповідності з даними додатків, саме тому я кажу, що він його «підтримує».
Четвертий режим - ПОДІЯ (або КОНТРОЛЬ). Коли користувач вводить символ або натискає кнопку, ця подія ловиться і записується, а потім процедура deContents виконується в режимі EVENT. deButton отримує ідентифікатор свого елемента керування кнопкою з FIFO і запитує, чи це елемент управління, на який клацнули. Якщо воно було, воно повертає TRUE, щоб можна було виконати код дії. Якщо ні, він просто повертає FALSE. З іншого боку, deEdit(..., &myStringVar)
виявляє, чи подія була призначена для неї, і якщо це так, передає її елементу керування редагуванням, а потім копіює вміст елемента керування редагуванням у myStringVar. Між цією та звичайною обробкою UPDATE myStringVar завжди дорівнює вмісту елемента керування редагуванням. Ось як робиться "прив'язка". Ця ж ідея стосується смуг прокрутки, списків, комбінованих вікон та будь-якого елемента керування, що дозволяє редагувати дані програми.
Ось посилання на мою редакцію у Вікіпедії: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article