Ответ 1
перераб. Это моя N-я попытка объяснить это.
Предположим, что у вас есть простая детерминированная процедура, которая выполняется повторно, всегда следуя той же последовательности выполнения операторов или вызовов процедур. Процедура вызывает себя, пишут все, что они хотят последовательно, в FIFO, и они читают одинаковое количество байтов с другого конца FIFO, например: **
Вызываемые процедуры используют FIFO в качестве памяти, поскольку то, что они читают, совпадает с тем, что они пишут при предыдущем выполнении. Так что, если их аргументы будут отличаться на этот раз с последнего времени, они могут это увидеть и делать все, что захотят, с этой информацией.
Чтобы начать работу, должно быть начальное выполнение, в котором происходит только запись, без чтения. Симметрично должно быть окончательное исполнение, в котором происходит только чтение, без записи. Таким образом, существует "глобальный" регистр режима, содержащий два бита, один из которых позволяет читать и один, который позволяет записывать, например:
Первоначальное выполнение выполняется в режиме 01, поэтому выполняется только запись. Вызовы процедуры могут видеть режим, поэтому они знают, что нет предшествующей истории. Если они хотят создавать объекты, которые они могут, и помещают идентифицирующую информацию в FIFO (нет необходимости хранить в переменных).
Промежуточные исполнения выполняются в режиме 11, поэтому происходит как чтение, так и запись, а вызовы процедур могут обнаруживать изменения данных. Если есть объекты, которые нужно обновлять, их идентификационная информация считывается и записывается в FIFO, поэтому они могут быть доступны и, при необходимости, изменены.
Окончательное исполнение выполняется в режиме 10, поэтому происходит только чтение. В этом режиме вызовы процедур знают, что они просто убираются. Если были сохранены какие-либо объекты, их идентификаторы считываются из FIFO, и их можно удалить.
Но реальные процедуры не всегда соответствуют одной и той же последовательности. Они содержат утверждения IF (и другие способы изменения того, что они делают). Как это можно обработать?
Ответ - это особый тип оператора IF (и его завершающая инструкция ENDIF). Вот как это работает. Он записывает логическое значение своего тестового выражения и считывает значение, которое испытательное выражение имело в прошлый раз. Таким образом, он может определить, изменилось ли тестовое выражение и принять меры. Требуемое действие - временно изменить регистр режима.
В частности, x является предшествующим значением тестового выражения, считанным из FIFO (если чтение включено, else 0), а y - текущее значение тестового выражения, записанное в FIFO (если запись включена), (На самом деле, если запись не включена, тестовое выражение даже не оценивается, а y равно 0.) Тогда x, y просто MASKs регистр режима r, w. Поэтому, если тестовое выражение изменилось с True на False, тело выполняется в режиме только для чтения. И наоборот, если он изменился с False на True, тело выполняется в режиме только для записи. Если результат равен 00, код внутри инструкции IF..ENDIF пропускается. (Возможно, вы захотите немного подумать о том, охватывает ли это все случаи - он это делает.)
Это может быть не очевидно, но эти утверждения IF..ENDIF могут быть произвольно вложенными и могут быть расширены для всех других условных операторов, таких как ELSE, SWITCH, WHILE, FOR и даже для вызова функций на основе указателей. Кроме того, процедура может быть разделена на подпроцедуры в любой желаемой степени, включая рекурсивную, до тех пор, пока выполняется режим.
(Существует правило, которое необходимо соблюдать, называемое правилом режима стирания, которое заключается в том, что в режиме 10 никакое вычисление каких-либо последствий, таких как слежение за указателем или индексирование массива, должно быть выполнено. что режим 10 существует только для того, чтобы избавиться от всего.)
Таким образом, это интересная структура управления, которая может быть использована для обнаружения изменений, как правило, изменений данных и принятия мер по этим изменениям.
Его использование в графических пользовательских интерфейсах заключается в том, чтобы сохранить некоторый набор элементов управления или других объектов в соответствии с информацией о состоянии программы. Для этого использования три режима называются SHOW (01), UPDATE (11) и ERASE (10). Процедура изначально выполняется в режиме SHOW, в которой создаются элементы управления, а информация, относящаяся к ним, заполняет FIFO. Затем любое количество исполнений выполняется в режиме UPDATE, где элементы управления изменяются по мере необходимости, чтобы оставаться в курсе состояния программы. Наконец, выполняется выполнение в режиме ERASE, в котором элементы управления удаляются из пользовательского интерфейса, а FIFO освобождается.
Преимущество этого заключается в том, что после того, как вы написали процедуру для создания всех элементов управления, как функцию состояния программы, вам не нужно писать что-либо еще, чтобы впоследствии обновлять или очищать. Все, что вам не нужно писать, означает меньше возможностей совершать ошибки. (Существует простой способ обработки пользовательских событий ввода без необходимости писать обработчики событий и создавать имена для них. Это объясняется в одном из видео, приведенных ниже.)
Что касается управления памятью, вам не нужно составлять имена переменных или структуру данных для хранения элементов управления. Он использует только достаточное количество хранилища в любой момент для видимых в данный момент элементов управления, а потенциально видимые элементы управления могут быть неограниченными. Кроме того, никогда не возникает никаких проблем с сборкой мусора ранее используемых элементов управления - FIFO действует как автоматический сборщик мусора.
С точки зрения производительности, когда он создает, удаляет или изменяет элементы управления, он тратит время, которое нужно потратить в любом случае. Когда он просто обновляет элементы управления, и нет изменений, циклы, необходимые для чтения, записи и сравнения, являются микроскопическими по сравнению с изменениями элементов управления.
Еще одно соображение о производительности и правильности относительно систем, которые обновляют дисплеи в ответ на события, заключается в том, что такая система требует, чтобы на каждое событие отвечали, и ни один из них дважды, иначе отображение будет неправильным, хотя некоторые последовательности событий могут быть самоотверждающимся. При дифференциальном исполнении обновления могут выполняться так часто или как редко, и дисплей всегда корректен в конце прохода.
Вот очень сокращенный пример, где есть 4 кнопки, из которых кнопки 2 и 3 являются условными для булевой переменной.
- В первом проходе в режиме просмотра логическое значение false, поэтому появляются только кнопки 1 и 4.
- Затем логическое значение имеет значение true, а pass 2 выполняется в режиме обновления, в котором кнопки 2 и 3 создаются, а кнопка 4 перемещается, давая тот же результат, что и логическое значение true на первом проходе.
- Затем логическое значение устанавливается в false, а проход 3 выполняется в режиме обновления, заставляя кнопки 2 и 3 удаляться, а кнопка 4 - назад, где она была раньше.
- Наконец, проход 4 выполняется в режиме Erase, в результате чего все исчезает.
(В этом примере изменения отменены в обратном порядке по мере их выполнения, но это необязательно. Изменения могут быть сделаны и отменены в любом порядке.)
Обратите внимание, что во все времена FIFO, состоящий из старого и нового, объединенного вместе, содержит точно параметры видимых кнопок плюс логическое значение.
Точка зрения заключается в том, чтобы показать, как единая "краска" процедура также может быть использована без изменений для произвольного автоматического инкрементного обновления и стирания.
Надеюсь, что ясно, что он работает для произвольной глубины вызовов подпроцедур и произвольного вложения условных выражений, включая циклы switch
, while
и for
, вызывая функции на основе указателей и т.д.
Если я должен это объяснить, тогда я открыт для потшотов, чтобы сделать объяснение слишком сложным.
Наконец, есть несколько сырых, но коротких видео, размещенных здесь.
** Технически, они должны прочитать такое же количество байтов, которые они написали в прошлый раз. Так, например, они могли бы написать строку, которой предшествует счетчик символов, и это ОК.
ADDED: Мне потребовалось много времени, чтобы быть уверенным, что это всегда будет работать. Я наконец доказал это. Он основан на свойстве Sync, что примерно означает, что в любой точке программы количество байтов, записанных на предыдущем проходе, равно числу, читаемому на последующем проходе. Идея доказательства состоит в том, чтобы сделать это путем индукции по длине программы. Самый сложный пример для доказательства - это раздел программы, состоящий из s1, за которым следует IF (тест) s2 ENDIF, где s1 и s2 являются подразделами программы, каждая из которых удовлетворяет свойству Sync. Сделать это в текстовом режиме - это остекление, но здесь я попытался его нарисовать:
Он определяет свойство Sync и показывает количество байтов, записанных и прочитанных в каждой точке кода, и показывает, что они равны. Ключевыми моментом является то, что 1) значение тестового выражения (0 или 1), прочитанного на текущем проходе, должно равняться значению, записанному на предыдущем проходе, и 2) выполняется условие Sync (s2). Это удовлетворяет свойству Sync для комбинированной программы.