учебники, программирование, основы, введение в,

 

Свойства атомов и категории функций

Prog-выражения и циклы
Противопоставление функционального и императивного стилей программирования порой напоминает войну остроконечников с тупоконечниками. Существует большое число чисто теоретических работ, исследовавших соотношения между потенциалом того и другого подхода и пришедших к заключению о формальной сводимости в обе стороны при некоторых непринципиальных ограничениях на технику программирования. Методика сведения императивных программ в функциональные заключается в определении правил разметки или переписывания схемы программы в функциональные формы. Переход от функциональных программ к императивным технически сложнее: используется интерпретация формул над некоторой специально устроенной абстрактной машиной. На практике переложение функциональных программ в императивные выполнить проще, чем наоборот — может не хватать близких понятий.
С практической точки зрения любые конструкции стандартных языков программирования могут быть введены как функции, дополняющие исходную систему программирования, что делает их вполне легальными средствами в рамках функционального подхода. Надо лишь четко уяснить цену такого дополнения и его преимущества, обычно связанные с наследованием решений и привлечением пользователей. В первых реализациях Лиспа были сразу предложены специальные формы и структуры данных, служащие мостом между разными стилями программирования, а заодно смягчающие недостатки исходной, слишком идеализированной, схемы интерпретации S-выражений, выстроенной для учебных и исследовательских целей. Важнейшее такого рода средство, выдержавшее испытание временем — prog-форма, списки свойств атома и деструктивные операции, расширяющие язык программирования так, что становятся возможными оптимизирующие преобразования структур данных, программ и процессов, а главное — раскрутка систем программирования.
Применение prog-выражений позволяет писать «паскалеподобные» программы, состоящие из операторов, предназначенных для исполнения. (Точнее «алголоподобные», т.к. появились лет за десять до паскаля. Но теперь более известен паскаль.)
Для примера prog-выражения приводится императивное определение функции (Length *), сканирующей список и вычисляющей число элементов на верхнем уровне списка. Значение функции Length — целое число. Программу можно примерно описать следующими словами (Стилизация примера от МакКарти.):
«Это функция одного аргумента L. Она реализуется
программой с двумя рабочими переменными u и v.
Записать число 0 в v.
Записать аргумент L в u.
A: Если u содержит NIL, то программа выполнена и
значением является то, что сейчас записано в v.
Записать в u cdr от того, что сейчас в u.
Записать в v на единицу больше того, что
сейчас записано в v.
Перейти к A»
Эту программу можно записать в виде Паскаль-программы с несколькими подходящими типами данных и функциями. Строкам описанной выше программы в предположении, что существует библиотека Лисп-функций над списками на Паскале, соответствуют строки определения функции:
function LENGTH (L: list) : integer;
label A;
var U: list;
V: integer;
begin
V := 0;
U := l;
A: if null (U) then LENGTH := V;
U := cdr (U);
V := V+1;
goto A;
end;
Переписывая в виде S-выражения, получаем программу:
(defun LENGTH  (L)
(prog (U V)
(setq V 0)
(setq U L)
A (cond ((null U)(return V)))
(setq U (cdr U))
(setq V (+ 1 V))
(go A)
)  )

(LENGTH '(A B C D))
(LENGTH ‘((X . Y) A CAR (N B) (X Y Z)))
Последние две строки содержат тесты. Их значения четыре и пять, соответственно. Prog-форма имеет структуру, подобную определениям функций и процедур в Паскале: (PROG, список рабочих переменных , последовательность операторов и атомов ...) Атом в списке является меткой, локализующей оператор, расположенный вслед за ним. В приведенном примере метка A локализует оператор, начинающийся с COND.
Первый список после символа PROG называется списком рабочими переменных. При отсутствии таковых должно быть написано NIL или (). С рабочими переменными обращаются примерно как со связанными переменными, но они не могут быть связаны ни с какими значениями через lambda. Значение каждой рабочей переменной есть NIL, до тех пор, пока ей не будет присвоено что-нибудь другое.
Для присваивания рабочей переменной применяется форма SET. Чтобы присвоить переменной pi значение 3.14 пишется (SET (QUOTE PI)3.14). SETQ подобна SET, но она еще и блокирует вычисление первого аргумента. Поэтому (SETQ PI 3.14) — запись того же присваивания. SETQ обычно удобнее. SET и SETQ могут изменять значения любых переменных из а-списка более внешних функций. Значением SET и SETQ является значение их второго аргумента.
Обычно операторы выполняются последовательно. Выполнение оператора понимается как его вычисление при текущем а-списке и отбрасывание его значения. Операторы программы часто выполняются в большей степени ради действия, чем ради значения.
GO-форма, используемая для указания перехода (GO A) указывает, что программа продолжается оператором, помеченным атомом A, причем это A может быть и из внешнего выражения prog.
Условные выражения в качестве операторов программы обладают полезными особенностями. Если ни одно из пропозициональных выражений не истинно, то вместо указания на ошибку, происходящего во всех других случаях, программа продолжается оператором, следующим за условным выражением. Это справедливо лишь для условного выражения, находящегося на верхнем уровне Prog.
RETURN — нормальный конец программы. Аргумент return вычисляется, что и является значением программы. Никакие последующие операторы не вычисляются.
Формы Go, Set, Return могут применяться как операторы лишь на верхнем уровне PROG или внутри COND, находящегося на верхнем уровне PROG.
Если программа прошла все свои операторы, она оканчивается со значением NIL.
Prog-выражение, как и другие Лисп-функции, может быть рекурсивным.
Функция REV, обращающая список и все подсписки, столь же естественно пишется с помощью рекурсивного Prog-выражения.
function rev (x: list) :List; 
label A, B;
var y, z: list;
begin

A: if null (x) then rev := y;
z := cdr (x);
if atom (z) then goto B;
z := rev (z);

B:    y := cons (z, y);
x := cdr (x);
goto A;
end;
Функция rev обращает все уровни списка, так что rev от (A ((B C) D)) даст ((D (C B))A).
(DEFUN rev (x)
(prog (y z)

A   (COND ((null x)(return y)))
(setq z (CDR x))
(COND ((ATOM z)(goto B)))
(setq z (rev z))

B       (setq y (CONS z y))
(setq x (CDR x))
(goto A)
))
Для того чтобы форма prog была полностью законна, необходима возможность дополнять а-список рабочими переменными. Кроме того, операторы этой формы требуют специального расширения языка — в него включаются формы go, set и return, не известные вне prog. Атомы, играющие роль меток, работают как указатели помеченного блока.
Кроме того, уточнен механизм условных выражений: отсутствие истинного предиката не препятствует формированию значения cond -оператора, т.к. все операторы игнорируют выработанное значение. Это позволяет считать, что значением является Nil. Такое доопределение условного выражения приглянулось и в области обычных функций, где оно часто дает компактные формулы для рекурсии по списку.
В принципе, SET и SETQ могут быть реализованы с помощью a-списка примерно так же, как и поиск значения, только с копированием связей, расположенных ранее изменяемой переменной (см. функцию assign из лекции 3). Более эффективная реализация будет описана ниже.
(DEFUN set (x y) (assign x y Alist))
Обратите внимание, что введенное таким образом присваивание работает разнообразнее, чем традиционное: обеспечена вычисляемость левой части присваивания, т.е. можно в программе вычислять имена переменных, значение которых предстоит поменять.
(setq x 'y)
(set x 'NEW)
(print x)
(print y)
Пример 6.1.
Напечатается Y и NEW (традиционно атомы печатаются заглавными буквами).

Списки свойств атомов и структура списков
До сих пор атом рассматривался только как уникальный указатель, обеспечивающий быстрое выяснение различимости имен, названий или символов. В настоящем разделе описываются списки свойств, которые начинаются или находятся в указанных ячейках.
Каждый атом имеет список свойств. Когда атом читается (вводится) впервые, тогда для него создается пустой список свойств, который потом можно заполнять. Список свойств устроен как специальная структура, подобная записям в Паскале, но указатели в такой записи сопровождаются тэгами, символизирующими тип хранимой информации. Первый элемент этой структуры расположен по адресу, который задан в указателе. Остальные элементы доступны по этому же указателю с помощью ряда специальных функций. Элементы структуры содержат различные свойства атома. Каждое свойство помечается атомом, называемым индикатором, или расположено в фиксированном поле структуры.
В первых реализациях Лиспа все системные свойства объектов располагались по единой схеме. Перечислим некоторые из индикаторов таких свойств.

  • PNAME — печатное имя атома для нужд ввода-вывода.
  • EXPR — S-выражение, определяющее функцию с именем атом, в списке свойств которого встречается EXPR.
  • SUBR — функция, определенная подпрограммой на машинном языке.
  • APVAL — постоянное значение атома, рассматриваемого как переменная.

В списке свойств атома NIL было два индикатора, PNAME и APVAL, со значением NIL.
Более детально диаграммы списков свойств приведены в описании Lisp 1.5.
Здесь достаточно принять к сведению, что реализация атомарных объектов — это сложная структура данных, в свою очередь представленная списками.
С помощью функции get в форме (get x i) можно найти для атома x свойство, индикатор которого равен i.
Значением (get ‘FF ‘EXPR) будет (LAMBDA (X) (COND ... )), если определение FF было предварительно задано с помощью (defun FF (X)( COND ... ).
Свойство с его индикатором может быть вычеркнуто - удалено из списка функцией remprop в форме (remprop x i).
С середины 70-х годов возникла тенденция повышать эффективность разработкой специальных структур, отличающихся в разных реализациях. Существуют реализации, например, muLisp, допускающие работу с представлениями атома как с обычными списками посредством функций car, cdr.
Согласно стандарту Common Lisp, глобальные значения переменных и определения функций хранятся в фиксированных полях структуры атома. Они доступны с помощью специальных функций, symbol-function и symbol-value. Список произвольных свойств можно получить с использованием функции symbol-plist. Функция remprop в Clisp удаляет лишь первое вхождение заданного свойства. Новое свойство можно ввести формой вида:
(setf (get ATOM INDICATOR ) PROPERTY )
Числа представляются в Лиспе как специальный тип атома. Атом такого типа состоит из указателя с тэгом, специфицирующим слово как число, тип этого числа и адрес собственно числа произвольной длины. В отличие от обычного атома одинаковые числа при хранении не совмещаются.
До этого момента списки рассматривались на уровне текстового ввода-вывода. В настоящем разделе анализируется кодовое представление списков внутри машины и сборщик мусора, обеспечивающий повторное использование памяти.

Представление структуры списка
В машине списки хранятся не как последовательности символов, а как структурные формы, построенные из машинных слов как частей деревьев, подобно записям в Паскале при реализации односвязных списков. При изображении структуры списка машинное слово рисуется как прямоугольник, разделенный на две части: адрес и декремент
В точности такую структуру непосредственно текстом представить невозможно, но ее можно построить с помощью одного из выражений выражений:
(let ((a '(M . N))) (setq b (list a 'X a)) )
((lambda (a) (list a 'X a) )'(M . N))
Циклические списки обычно не поддерживаются. Такие списки не могут быть введены псевдо-функцией read, однако они могут возникнуть как результат вычисления некоторых функций, точнее, применения структуроразрушающих или деструктивных функций. Печатное изображение таких списков имеет неограниченную длину. Например, структура
может распечатываться как ( A B C A B C ... ).
Преимущества структур списков для хранения S-выражений в памяти:

  1. Размер и даже число выражений, с которыми программа будет иметь дело, можно не предсказывать. Кроме того, можно не организовать для размещения выражений блоки памяти фиксированной длины.
  2. Ячейки можно переносить в список свободной памяти, как только исчезнет необходимость в них. Даже возврат одной ячейки в список имеет значение, но если выражения хранятся линейно, то организовать использование лишнего или освободившегося пространства из блоков ячеек трудно.
  3. Выражения, являющиеся продолжением нескольких выражений, могут быть предоставлены только однажды.

Ниже следует простой пример, иллюстрирующий точность построения структур списка. Показаны два типа структур списка и описаны на Лиспе функции для преобразования одного типа в другой.
Предполагается, что дан список вида
L1 = ((A B C)(D E F ) ... (X Y Z))
представленный как
и нужно построить список вида
L2 = ((A (B C))(D (E F)) ... (X (Y Z)))
представленный как
Рассмотрим типичную подструктуру (A (B C)) второго списка.
Она может быть построена из A,B и C с помощью
(cons ‘A (cons (cons ‘B (cons ‘C NIL)) NIL))
или, используя функцию list, можно то же самое записать как
(list ‘A (list ‘B ‘C))
В любом случае дан список x из трех атомов x = (A B C), аргументы A, B и C, используемые в предыдущем построении, можно найти как
A = (car x)
B = (cadr x)
C = (caddr x)
Первый шаг в получении L2 из L1 — это определение функции grp, строящей (X (Y Z)) из списка вида (X Y Z).
(grp x) = (list (car x) (list (cadr x) (caddr x)))
Здесь grp применяется к списку L1 в предположении, что L1 заданного вида.
Для достижения цели новая функция mltgrp определяется как
(mltgrp L) = (COND ((null L) NIL)
(T (cons (grp (car L)) (mltgrp (cdr L)) )))
Итак, mltgrp, применяемая к списку L1, перебирает (X Y Z) по очереди и применяет к ним grp, чтобы установить их новую форму (X (Y Z)) до тех пор, пока не завершится список L1 и не построится новый список L2.

Деструктивные (разрушающие) преобразования структуры списков
Теория рекурсивных функций, изложенная в лекции 2, будет упоминаться далее как строгий, чистый или элементарный Лисп. Хотя этот язык универсален в смысле теории вычислимых функций от символьных выражений, он непрактичен как система программирования без дополнительного инструмента, увеличивающего выразительную силу и эффективность языка.
В частности, элементарный Лисп не имеет возможности модифицировать структуру списка. Единственная базисная функция, влияющая на структуру списка — это cons, а она не изменяет существующие списки, только создает новые. Функции, описанные в чистом Лиспе, такие как subst, в действительности не модифицируют свои аргументы, но делают модификации, копируя оригинал.
Элементарный Лисп работает как расширяемая система, в которой информация как бы никогда не пропадает. Set внутри Prog лишь формально смягчает это свойство, сохраняя ассоциативный список и моделируя присваивания теми же CONS.
Теперь же Лисп обобщается с точки зрения структуры списка добавлением структуроразрушающих средств — деструктивных базисных операций над списками rplaca и rplacd. Эти операции могут применяться для замены адреса или декремента любого слова в списке. Они используются ради их действия так же, как и ради их значения и называются псевдо-функциями.
(rplaca x y) заменяет адрес из x на y, эквивалент (car x) := y. Ее значение — x, но x, несколько отличающийся от того, что было раньше. На языке значений rplaca можно описать равенством
(rplaca x y) = (cons y (cdr x))
Но действие различное: никакие cons не вызываются и новые слова не создаются.
(rplacd x y) заменяет декремент x на y, эквивалент (cdr x) := y.
Эти операции должны применяться с осторожностью! Они могут в корне преобразить существующие определения и основную память. Их применение может породить циклические списки, приводящие к бесконечной печати или выглядящие бесконечными для таких функций как equal и subst.
Деструктивные функции используются при реализации списков свойств атома и ряда эффективных, но небезопасных, функций Clisp-а, таких как nconc, mapc и т.п.
Для примера рассмотрим функцию mltgrp. Это преобразующая список функция, которая преобразует копию своего аргумента. Подфункция grp реорганизует подструктуру
Пусть новое машинное слово строит (cons (cadr x) (cddr x)). Тогда указатель на него встраивается в x при вычислении формы:
(rplaca (cdr x) (cons (cadr x) (cddr x)))
Другое изменение состоит из удаления указателя на третье слово из второго слова.
Это выполняется как вычисление формы (rplaca (cdr x) NIL).
Функция pgrp теперь определяется как соотношение:
(pgrp x) = (rplacd (rplaca (cdr x) (cons (cadr x)
(cddr )x))) NIL)
Функция pgrp используется, в сущности, ради ее действия. Ее значением, неиспользуемым, является подструктура ((B C)). Поэтому для mltgrp необходимо, чтобы pgrp выполнялось, а ее значение игнорировалось. Поскольку верхний уровень не должен копироваться, mltgrp не обязательно должна основываться на cons.
(pmltgrp L) =(COND ((null L) NIL)
(T  (pgrp (cdr L)) (pmltgrp (cdr L)) )))
prog2 — функция, вычисляющая свои аргументы. Ее значение — второй аргумент.
Значение pmltgrp — NIL. pgrp и pmltgrp — псевдо-функции.
Список свободной памяти и сборщик мусора
В любой момент времени только часть памяти, отведенной для структур списков, действительно используется для хранения S-выражений. Остальные ячейки организованы в простой список, называемый списком свободной памяти.
Первые реализации действовали по следующей схеме.
Определенный регистр FREE содержит информацию о первой ячейке этого списка. Когда требуется слово для формирования дополнительной структуры списка, берется первое слово списка свободной памяти, а код в регистре FREE заменяется на информацию о втором слове списка свободной памяти. Не требуется никаких программных средств для того, чтобы пользователь программировал возврат ячеек в список свободной памяти. Этот возврат происходит автоматически при пропуске программы, где бы ни исчерпался список свободной памяти. Программа, восстанавливающая память, называется «мусорщик». Любая часть структуры списка, доступная программе, рассматривается как активный список и не затрагивается мусорщиком. Активные списки доступны программе через некоторые фиксированные наборы базисных ячеек, таких как ячейки списка атомов и ячейки, вмещающие частичные результаты Лисп-выражений. Сложные структуры списков могут быть произвольной длины, но каждое активное слово должно быть подведено к базисной ячейке цепью car-cdr.
Любая ячейка, не достижимая таким образом, недоступна для программы и не активна, поэтому ее содержимое не представляет интереса. Неактивные, то есть недоступные программе ячейки восстанавливаются мусорщиком в списке свободной памяти следующим образом. Во-первых, каждая ячейка, к которой можно получить доступ через цепь car-cdr, метится установлением отрицательного знака. Где бы ни выявилось отрицательное слово в цепи во время процесса пометки, мусорщик знает, что остаток раскручиваемого списка содержит уже помеченные ячейки. Затем мусорщик предпринимает линейное выметание осовободившегося пространства, собирая ячейки с положительным знаком в новый список свободной памяти и восстанавливая исходный знак активных ячеек.
Иногда структуры списка указывают на полные слова, такие как печатные имена и числа. Мусорщик не может пометить эти слова, потому что знаковый разряд использован. Мусорщик должен прекратить работу, потому что указатели, расположенные в адресе и декременте таких слов, бессмысленны.
В этом случае проблемы повторного использования памяти решаются расположением полных слов в зарезервированной области памяти, называемой областью полных слов. Мусорщик должен прекращать прослеживание, как только покидает поле свободной памяти. Пометка в поле полных слов осуществляется таблицей битов.
Такая реализация экономична в отношении памяти, но она имеет ряд неприятных следствий: непредсказуемые длинноты (время работы) при поиске очередной порции ячеек и «перегрев системы», если такие порции слишком малы для продолжения счета. По мере роста производительности оборудования разработаны простые и не столь обременительные алгоритмы повторного использования памяти на базе параллельных процессов и профилактического копирования активных структур данных в дополнительные блоки памяти. Такие методы не требуют сложной разметки и анализа достижимости. Содержательная аналогия с мастерским мытьем посуды, то есть не допуская переполнения раковины, вполне отражает метод stop© принятый в современных реализациях.

Гибкий интерпретатор
В качестве примера повышения гибкости определений приведено упрощенное определение Лисп-интерпретатора на Лиспе, полученное из М-выражения, приведенного Дж. Мак-Карти в описании Lisp 1.5
Ради удобочитаемости здесь уменьшена диагностичность, нет предвычислений и (формы Prog. Лисп хорошо приспособлен к оптимизации программ. Любые совпадающие подвыражения можно локализовать и вынести за скобки, как можно заметить по передаче значения
"(ASSOC e al )".
Определения функций хранятся в ассоциативном списке, как и значения переменных.
Функция SUBR — вызывает примитивы, реализованные другими, обычно низкоуровневыми, средствами. ERROR — выдает сообщения об ошибках и сведения о контексте вычислений, способствующие поиску источника ошибки. Уточнена работа с функциональными аргументами:
(DEFUN EVAL (e al )
(COND

((EQ e NIL ) NIL )
((ATOM e )((LAMBDA (v )
(COND (v (CDR v ) )
(T (ERROR 'undefvalue )) )
) (ASSOC e al ) )
)
((EQ (CAR e) 'QUOTE ) (CAR (CDR e )) )
((EQ (CAR e) 'FUNCTION )
(LIST 'FUNARG (CADR fn ) al ) )
((EQ (CAR e) 'COND ) (EVCON (CDR e ) al ) )
(T (apply (CAR e)(evlis (CDR e) al ) al ) )
) )

(DEFUN APPLY (fn args al )
(COND
((EQ e NIL ) NIL )
((ATOM fn )
(COND
((MEMBER fn '(CAR CDR CONS ATOM EQ ))
(SUBR fn agrs al ))
(T (APPLY (EVAL fn al ) args al ))
) )

        ((EQ (CAR fn ) 'LABEL )
(APPLY (CADDR fn )
args
(CONS (CONS (CADR fn )(CADDR fn ))
al ) ) )

((EQ (CAR fn ) 'FUNARG )
(APPLY (CDR fn ) args (CADDR fn)) )

((EQ (CAR fn ) 'LAMBDA )
(EVAL (CADDR fn )
(APPEND (PAIR (CADR fn ) args ) al ))
(T (APPLY (EVAL fn al ) args al ))
) )
Определения assoc, append, pair, list — стандартны.
Примерно то же самое обеспечивают eval-p и apply-p, рассчитанные на использование списков свойств атома для хранения постоянных значений и функциональных определений. Индикатор category указывает в списке свойств атома на правило интерпретации функций, относящихся к отдельной категории, macro — на частный метод построения представления функции. Функция value реализует методы поиска текущего значения переменной в зависимости от контекста и свойств атомов.
(defun eval-p (e c)
(cond ((atom e) (value e c))
((atom (car e))(cond ((get (car e) 'category)
((get (car e) 'category) (cdr e) c) )
(T (apply-p (car e)(evlis (cdr e) c) c))
) )
(T (apply-p (car e)(evlis (cdr e) c) c))
))

(defun apply-p (f args c)
(cond ((atom f)(apply-p (function f c) args c))
((atom (car f))(cond ((get (car f) 'macro)
(apply-p ((get (car f) 'macro)
(cdr f) c) args c))
(T (apply-p (eval f e) args c))
) )
(T (apply-p (eval f e) args c))
))
Или то же самое с вынесением общих подвыражений во вспомогательные параметры:
(defun eval-p (e c)
(cond ((atom e) (value e c))
((atom (car e))
((lambda (v) (cond (v (v(cdr e) c) )
(T (apply-p (car e)(evlis (cdr e) c) c))
)) (get (car e) 'category) ) )
(T (apply-p (car e)(evlis (cdr e) c) c))
))

(defun apply-p (f args c)
(cond ((atom f)(apply-p (function f c) args c))
((atom (car f))
((lambda (v) (cond (v (apply-p (v (cdr f)
c) args c))
(T (apply-p (eval f e) args c))
))(get (car f) 'macro) ))
(T (apply-p (eval f e) args c))
))
Расширение системы программирования при таком определении интерпретации осуществляется простым введением/удалением соответствующих свойств атомов и функций.
Полученная схема интерпретации допускает разнообразные категории функций и макросредства конструирования функций, позволяет задавать различные механизмы передачи параметров функциям. Так, в языке Clisp различают, кроме обычных, обязательных и позиционных, — необязательные (факультативные), ключевые и серийные(многократные, с переменным числом значений) параметры . Виды параметров обозначаются пометкой &optional, &key и &rest соответственно, размещаемой перед параметрами в lambda списке формальных аргументов. При этом серийный параметр должен быть последним в списке.
(defun funcall (fn rest agrs) (apply fn args))
Необязательный параметр может иметь начальное значение, устанавливаемое по умолчанию, т.е. если этот параметр не задан при вызове функции. При отсутствии начального значения его роль играет Nil.
(defun ex-opt (space optional dot (line 'x))
(list space 'of dot 'and- line))
(ex-opt 'picture)
(ex-opt 'picture 'circle)
(ex-opt 'picture 'circle 'bars)
Пример 6.2.
Ключевые параметры, являясь необязательными, не зависят еще и от порядка вхождения в список аргументов. Незаданные аргументы по умолчанию связываются с Nil.
(defun keylist (a key x y z) (list a x y z))
(keylist 1 :y 2) ;= (1 NIL 2 NIL)
(defun LENGTH (L optional (V 0))
(cond ((null L) V)
(T (+ 1 (LENGTH (cdr L))))
)  )

((LENGTH '(1 2) 3)      ; = 5

(defun REVERSE (L optional (m Nil))
(cond ((null L) m)
(T (REVERSE (cdr L) (cons (car L) m) ))
)  )

(REVERSE '(1 2 3))            ; = (3 2 1)
(REVERSE '(1 2 3) '(5 6))   ;= (3 2 1 5 6)
Пример 6.3.
Такой подход к работе параметрами часто освобождает от необходимости во вспомогательных функциях, что упрощает и определение Eval от обязательности упоминания а-списка. Если воспользоваться сводимостью (COND) к Nil при отсутствии истиного предиката и кое-где использовать отображения, то определение становится совсем компактным.

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика