Ну и дела, Брайан, я бы хотел увидеть ваш вопрос раньше. Так как это в значительной степени мой
«изобретение» (к лучшему или к худшему), я мог бы помочь.
Вставлено: самое короткое из возможных
Я могу объяснить, что если
нормальное исполнение похоже на бросание
мяч в воздухе и ловить его, то
дифференциальное исполнение походит на
жонглирование.
@ Объяснение Windfinder отличается от моего, и это нормально. Этот метод не так прост, чтобы обернуть голову, и мне потребовалось около 20 лет (время от времени), чтобы найти объяснения, которые работают. Позвольте мне сделать еще один снимок здесь:
Мы все понимаем простую идею, что компьютер шагает по программе, берет условные ветви на основе входных данных и что-то делает. (Предположим, что мы имеем дело только с простым структурированным кодом goto-less, return-less.) Этот код содержит последовательности операторов, базовые структурированные условия, простые циклы и вызовы подпрограмм. (Забудьте о функциях, возвращающих значения на данный момент.)
Теперь представьте, что два компьютера выполняют один и тот же код в режиме блокировки друг с другом и могут сравнивать записи. Компьютер 1 работает с входными данными A, а компьютер 2 работает с входными данными B. Они работают шаг за шагом, бок о бок. Если они приходят к условному утверждению, такому как IF (test) .... ENDIF, и если у них есть разногласие относительно того, является ли тест истинным, то тот, кто говорит тест, если false, переходит к ENDIF и ждет его сестра, чтобы наверстать упущенное. (Вот почему код структурирован, поэтому мы знаем, что сестра в конечном итоге доберется до ENDIF.)
Поскольку два компьютера могут общаться друг с другом, они могут сравнивать записи и давать подробное объяснение того, как два набора входных данных и истории выполнения различаются.
Конечно, в дифференциальном исполнении (DE) это делается с одним компьютером, имитируя два.
СЕЙЧАС, предположим, что у вас есть только один набор входных данных, но вы хотите увидеть, как они менялись со времени 1 во время 2. Предположим, что выполняемая вами программа является сериализатором / десериализатором. При выполнении вы одновременно сериализуете (записываете) текущие данные и десериализуете (читаете) прошлые данные (которые были записаны в последний раз, когда вы это делали). Теперь вы можете легко увидеть, в чем разница между данными, которые были в прошлый раз, и тем, что происходит в этот раз.
Файл, в который вы записываете, и старый файл, из которого вы читаете, вместе взятые, составляют очередь или FIFO («первым пришел - первым обслужен»), но это не очень глубокая концепция.
Мне пришло в голову то время, когда я работал над графическим проектом, где пользователь мог создавать небольшие подпрограммы дисплея-процессора, называемые «символами», которые можно было бы собрать в более крупные подпрограммы, чтобы рисовать такие вещи, как диаграммы труб, резервуаров, клапанов и прочего как это. Мы хотели, чтобы диаграммы были «динамическими» в том смысле, что они могли бы постепенно обновляться, не перерисовывая всю диаграмму целиком. (Оборудование было медленным по сегодняшним стандартам.) Я понял, что (например) подпрограмма для рисования гистограммы может помнить ее старую высоту и просто постепенно обновлять себя.
Звучит как ООП, не так ли? Однако вместо того, чтобы «сделать» «объект», я мог бы воспользоваться предсказуемостью последовательности выполнения процедуры диаграммы. Я мог бы написать высоту бара в последовательном потоке байтов. Затем, чтобы обновить изображение, я мог бы просто запустить процедуру в режиме, в котором она последовательно считывает свои старые параметры, в то время как записывает новые параметры, чтобы быть готовой к следующему этапу обновления.
Это кажется глупо очевидным и, казалось бы, сломается, как только процедура содержит условное выражение, потому что тогда новый поток и старый поток выйдут из синхронизации. Но потом до меня дошло, что, если они также сериализуют логическое значение условного теста, они могут вернуться обратно синхронно .Потребовалось некоторое время, чтобы убедить себя, а затем доказать, что это будет всегда работать, при условии соблюдения простого правила («правило стирания»).
Конечным результатом является то, что пользователь может создавать эти «динамические символы» и собирать их в более крупные диаграммы, не заботясь о том, как они будут динамически обновляться, независимо от того, насколько сложным или структурно изменяемым будет отображение.
В те дни мне приходилось беспокоиться о помехах между визуальными объектами, чтобы удаление одного не повредило другим. Однако теперь я использую технику с элементами управления Windows и позволяю Windows позаботиться о проблемах рендеринга.
Так чего же он добивается? Это означает, что я могу создать диалог, написав процедуру для рисования элементов управления, и мне не нужно беспокоиться о том, чтобы на самом деле запоминать объекты элемента управления или иметь дело с их постепенным обновлением, или заставлять их появляться / исчезать / двигаться в зависимости от условий. В результате получается намного меньший и более простой исходный код диалога, примерно на порядок, и такие вещи, как динамическое расположение или изменение количества элементов управления или наличие массивов или сеток элементов управления, являются тривиальными. Кроме того, такой элемент управления, как поле «Редактировать», может быть тривиально связан с данными приложения, которые он редактирует, и он всегда будет достоверно корректным, и мне никогда не придется иметь дело с его событиями. Помещение в поле редактирования строковой переменной приложения - это редактирование в одну строку.
- Почему это трудно понять?
Самое сложное, что я нашел для объяснения, это то, что это требует другого подхода к программному обеспечению. Программисты настолько твердо привязаны к программному представлению «объект-действие», что хотят знать, что это за объекты, каковы классы, как они «строят» отображение и как они обрабатывают события, что это требует вишни бомба, чтобы взорвать их из этого. Я пытаюсь донести, что на самом деле важно что вы хотите сказать? Представьте, что вы создаете язык, специфичный для предметной области (DSL), где все, что вам нужно сделать, это сказать: «Я хочу отредактировать» переменная A здесь, переменная B там, и переменная C там внизу ", и это волшебным образом позаботится об этом за вас. Например, в Win32 есть этот «язык ресурсов» для определения диалогов. Это очень хороший DSL, за исключением того, что он не заходит достаточно далеко. Он не «живет» на основном процедурном языке, не обрабатывает события для вас и не содержит циклов / условных выражений / подпрограмм. Но это хорошо, и Dynamic Dialogs пытается завершить работу.
Итак, другой способ мышления таков: чтобы написать программу, вы сначала находите (или изобретаете) соответствующий DSL и кодируете как можно большую часть своей программы. Пусть it имеет дело со всеми объектами и действиями, которые существуют только для реализации.
Если вы хотите по-настоящему понять дифференциальное выполнение и использовать его, есть пара хитрых вопросов, которые могут сбить вас с толку. Однажды я закодировал его в макросах 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 }
Добавлено: Брайан задал хороший вопрос, и я подумал, что ответ относится к основному тексту здесь:
@ Майк: Мне неясно, что на самом деле делает оператор if (de, Button (50, 20, Add))). Что делает функция deButton? Кроме того, ваши циклы FOR / END используют какой-то макрос или что-то еще? - Брайан.
@ Брайан: Да, операторы FOR / END и IF являются макросами. Проект SourceForge полностью реализован. Дебуттон поддерживает кнопку управления. Когда происходит какое-либо действие пользовательского ввода, код запускается в режиме «события управления», в котором 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