Введение в программирование на Лиспе

  35790931     

с последующим их суммированием. Обычная


(defun ряд_цел (M N) (cond ((> M N) Nil) (T(cons M (ряд_цел (1+ M) N))))) (defun сумма (X) (cond ((= X 0) 0) (T (+ (car X)( сумма (cdr X))))) )
Пример 9.1. Построение ряда целых от M до N с последующим их суммированием. Обычная формула:
Закрыть окно



defun ряд_цел


( defun ряд_цел (M N) (cond ((> M N) Nil)
(T(cons M (ряд_цел (1+ M) N)))))
(defun сумма (X) (cond ((= X 0) 0)
(T (+ (car X)( сумма (cdr X))))) )

Функция над целыми, начиная


(defun цел (M) (cons M ( || (цел (1+ M) ))))
Пример 9.2. Функция над целыми, начиная с М:
Закрыть окно



в которой возможно данное событие


(Defun super () ; Внешний уровень – обработчик внутренних событий (catch 'abort ; Имя обрабатваемого внутреннего события (sub) ; Вызов формы, в которой возможно данное событие (print "It is impossible") ; Реакция на событие ) ) (Defun sub () ; Внутренний уровень (throw 'abort 99) ; Вызов события ) (super) ; Вызов формы, контролирующей внутренние события.
Пример 9.3. Обработка событий при взаимодействии функций
Закрыть окно



в которой возможно данное




(Defun super () ; Внешний уровень – обработчик внутренних событий
(catch 'abort ; Имя обрабатваемого внутреннего события
(sub) ; Вызов формы, в которой возможно данное событие
(print "It is impossible") ; Реакция на событие
) )
(Defun sub () ; Внутренний уровень
(throw 'abort 99) ; Вызов события
)
(super) ; Вызов формы, контролирующей внутренние события.

Работа с событиями


Наиболее общая модель организации процессов сводится к определению реакций на происходящие события. Событий конечное число. Работа с событиями в системе Clisp обеспечивается парой функций:

Throw – вызов события.

Catch – обработка события (реакция на событие).

Процесс с событиями проиллюстрирован следующим примером Грехема [ 10 ] как взаимодействие функций, работающих на разных уровнях:

(Defun super () ; Внешний уровень – обработчик внутренних событий (catch 'abort ; Имя обрабатваемого внутреннего события (sub) ; Вызов формы, в которой возможно данное событие (print "It is impossible") ; Реакция на событие ) ) (Defun sub () ; Внутренний уровень (throw 'abort 99) ; Вызов события ) (super) ; Вызов формы, контролирующей внутренние события.

Пример 9.3. Обработка событий при взаимодействии функций (html, txt)

Таблица 9.1. Clisp: Функции управления вычислениями

(And Форма … )Вычисляет формы, пока не наткнетсяя на Nil
(Case Значение ( Ключ Форма) … )По значению, совпадающему с Ключем, выбирает форму, которую вычисляет
(Catch Атом Форма …)Работает в паре с throw. Устанавливает ловушку, идентифицируемую Атомом внутри Форм.
(Cond (Предикат Форма) … )Ветвление
(If Предикат Да-форма Нет-форма )Обычное условное выражение
(Or Форма … )Вычисляет формы , пока не найдет отличную от Nil/
(Throw Атом Форма )Работает в паре с Catch. Форма задает реакцию на обнаруженную ловушку Атом.
(Unless Предикат Форма … )Вычисляет формы если предикат имеет значение Nil
(When Предикат Форма … )Вычисляет формы при истином значении предиката.



Замедленные вычисления могут быть результативнее


Замедленные вычисления могут быть результативнее и эффективнее обычных.Хранимые вместе с данными рецепты их вычислений позволяют работать с бесконечными множествами.В ряде случаев возможно получение полного результата при неопределенных значениях частей.

Замедленные вычисления (lazy evaluation)


Интуитивное представление о вычислении выражений, согласно которому функция применяется к заранее вычисленным аргументам, не всегда гарантирует получение результата. Ради полноты вычислений, гибкости программ и результативности процессов такое представление можно расширить и ввести категорию специальных функций, которые "сами знают", когда и что из их аргументов следует вычислить. Примеры таких функций - специальные функции QUOTE и COND, которые могут анализировать и варьировать условия, при которых вычисление необходимо. Такие функции манипулируют данными, представляющими выражения, и явно определяют в программах позиции обращения к интерпретатору.

Источником неэффективности стандартных "прямолинейных" вычислений может быть целостность больших сложных данных, избыточное вычисление бесполезных выражений, синхронизация формально независимых действий. Такую неэффективность можно смягчить простыми методами замедленных вычислений (lazy evaluation). Необходимо лишь вести учет дополнительных условий для их фактического выполнения, таких как востребованность результата вычислений. Такие соображения по обработке параметров функций называют "вызов по необходимости".

Любое большое сложное данное можно вычислять "по частям". Вместо вычисления списка

(x1 x2 x3 ... )

можно вычислить x1 и построить структуру

(x1 (рецепт вычисления остальных элементов))

Получается принципиальная экономия памяти ценой незначительного расхода времени на вспомогательное построение. Рецепт - это ссылка на уже существующую программу, связанную с контекстом ее исполнения, т.е. с состоянием ассоциативного списка в момент построения рецепта.

(defun ряд_цел (M N) (cond ((> M N) Nil) (T(cons M (ряд_цел (1+ M) N))))) (defun сумма (X) (cond ((= X 0) 0) (T (+ (car X)( сумма (cdr X))))) )

Пример 9.1. Построение ряда целых от M до N с последующим их суммированием. Обычная формула: (html, txt)

Введем специальные операции || - приостановка вычислений и @ - возобновление ранее отложенных вычислений.
Приостановка сводится к запоминанию символьного представления программы, которая временно не вычисляется, но можно вернуться к ее вычислению с помощью операции возобновления. Отложенная программа преобразуется в так называемый рецепт вычисления, который можно хранить как данное и выполнять в любой момент.

В рецепте хранится не только вычисляемая форма, но и контекст, в котором первоначально было предусмотрено ее вычисление. Таким образом, гарантируется, что переход от обычной программы к программе с отложенными действиями не нарушает общий результат.

Избежать целостного представления большого и возможно бесконечного ряда чисел можно небольшим изменением формулы, отложив вычисление второго аргумента CONS "до лучших времен" – когда его значение действительно понадобится более внешней функции:

(defun ряд_цел (M N) (cond ((> M N) Nil) (T(cons M ( || (ряд_цел (1+ M) N))))))

(defun сумма (X) (cond ((= X 0) 0) (T (+ (car X)( @ ( сумма (cdr X))))) ))

Можно исключить повторное вычисление совпадающих рецептов. Для этого во внутренне представление рецепта вводится флаг, имеющий значение T - истина для уже выполненных рецептов, Nil - ложь для невыполненных.

Таким образом рецепт имеет вид (Nil e AL ) или ( T X ), где X = (eval e AL ) для произвольного e, в зависимости от того, понадобилось ли уже значение рецепта или еще не было в нем необходимости.

Это заодно дает возможность понятие данных распространить на бесконечные множества. Вместо привычного оперирования отрезками целых, не превосходящий заданное число М, можно манипулировать рядом целых, превосходящих M.

(defun цел (M) (cons M ( || (цел (1+ M) ))))

Пример 9.2. Функция над целыми, начиная с М: (html, txt)

Можно из таким образом организованного списка выбирать нужное количество элементов, например, первые K элементов можно получить по формуле:

(defun первые (K Int) (cond ((= Int Nil) Nil) ((= K 0) Nil) (T (cons (car Int)( первые ( @ (cdr Int))) )) ))

Формально эффект таких приостанавливаемых и возобновляемых вычислений получается следующей реализацией операций || и @:



||e = > (lambda () e ) @e = > (e ),

что при интерпретации дает связывание функционального аргумента с ассоциативным списком для операции || - приостановка и вызов функции EVAL для операции @ - возобновление вычислений.

Обычно в языках программирования различают вызовы по значению, по имени, по ссылке. Техника приостановки и возобновления функций может быть названа вызовом по необходимости.

При небольшом числе значений заданного типа, например, для истиностных значений, возможны вычисления с вариантами значений с последующим выбором реальной альтернативы пользователем, но это удобнее обсудить позднее, при изучении вариантов и недетерминированных вычислений.

Техника замедленных вычислений позволяет выполнять декомпозицию программы на обязательно выполнимые и возможно невыполнимые действия. Выполнимые действия в тексте определения замещаются на их результат, а невыполнимые преобразуются в остаточные, которые будут выполнены по мере появления дополнительной информации.

Многие выражения по смыслу используемых в них операций иногда определены при частичной определенности их операндов, что часто используется при оптимизации кода программ (умножение на 0, разность или частное от деления совпадающих форм и т.п.)

Отложенные действия - естественный механизм управления заданиями в операционных системах, а также программирования асинхронных и параллельных процессов.