1 Целью данной работы является изучение основных правил программирования на LISP, используя диалект MuLISP. Первые два урока будут посвящены изучению "чистого" MuLISP - это подмножество MuLISP, которое иллюстрирует многие из концепций программирования, необходимые для понимания MuLISP. Систематическое использование полной мощности MuLISP начинается с третьего урока. MuLISP - мощный язык программирования, богатый конструкциями управления и позволяющий работать с различными структурами данных. LISP - важнейший язык, используемый в исследованиях по исскуственному интеллекту и в математической лингвистике. Программы искусственного интеллекта отличаются от традиционных программ тем, что в них в первую очередь обрабатываются данные, представленные в символьном, а не в числовом виде. В обработке символьной информации важна не только форма рассматриваемых знаний, но и их содержание и значение. Если в численных вычислениях внимание уделяется количественной стороне и численным значениям данных, то в символьной обработке важна количественная сторона данных, т.е. особенности их строения и их функциональные особенности. В MuLISP фактически любая символьная структура данных может быть представлена как объект. Уроки начинаются с описания того, что составляет объект и как объекты могут использоваться, чтобы представлять символьнве структуры данных. После этого мы будем описывать как использовать MuLISP, чтобы управлять этими объектами. 2 УРОК № 1 Объекты данных могут быть простыми или составными. Простые объекты данных называются атомами. Атомы не могут быть разделены на подкомпоненты. Атомы имеют свои имена, которые состоят из строки символов. Пример: четыре типичных имен атомов: СТУЛ L 456 -456 Составные объекты данных называются списками. Списки могут состоять из нуля или большего количества объектов, каждый из которых может быть простым или составным. Элементы списка должны быть заключены в круглые скобки. Пример: $ ( ЯЗЫК ПРОГРАММИРОВАНИЯ БЕЙСИК) Кроме того, что MuLISP используется для символьной обработки данных, он также является и функциональным языком потому, что для манипуляции символьными структурами данных используются функции. "Чистый" MuLISP обеспечивает 5 примитивных функций для управления примитивными объектами данных /то есть атомами и списками/. Урок начинается с показа механизма взаимодействия с MuLISP, а затем рассматривается применение каждой из 5 функций. Работа с MuLISP напоминает использование карманного калькулятора. Когда на экране появляется приглашение - знак доллара {$}, вы можете ввести выражение. MuLISP читает это выражение, вычисляет его, и отображает результат. Чтобы предотвратить вычисление значения выражения, нужно поставить перед выражением апостроф. Пример: вводится имя атома СЛОВО $ 'СЛОВО Если имя атома вводится без апострофа, то значение атома возвращается. В "чистом" MuLISP атомы приравниваются к самим себе. Следовательно апостроф перед атомом требуется невсегда: $ 'СЛОВО Элементы списка заключаются в круглые скобки. Пример: ввода списка $ '(ЛИПА ТОПОЛЬ ЕЛЬ) Элементы списка сами могут быть списками. 3 Пример: $ '((ЛИПА ДЕРЕВО)(КУРИЦА ПТИЦА)(СТОЛ МЕБЕЛЬ)) Если апостроф перед списком отсутствует, то появится сообщение об ошибке "Неопределенная функция" . Для продолжения работы надо выбрать опцию 'Continue', нажатием 'С, и ошибка будет проигнорированна. Одна из наиболее общих операций - выбор одного элемента списка, чтобы иметь возможность исследовать его независимо. Функции, используемые для этой цели, называются селекторными. Это функция CAR, которая возвращает первый элемент списка, и функция CDR, которая возвращает все элементы списка за исключением первого. В MuLISP обращения к функции производятся в формате: (<name> <argl> <arg2> ...) , где <name> - имя функции, <argl> - первый аргумент, <arg2> - второй и т.д. Следует обратить внимание на одну из особенностей LISP: левая скобка ставится перед именем функции, а не после него, как в математике и других языках программирования. Пример использования команды CAR (первый элемент списка): $ (CAR '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ЛИПА Проверьте: 1. имя функции CAR набирается на верхнем регистре; 2. список начинается с апострофа; 3. соответствие открытых и закрытых скобок. (MuLISP игнорирует дополнительные ПРАВЫЕ круглые скобки, так что вы можете поставить их чуть больше в правый конец выражения.) Пример использования команды CDR (все элементы кроме первого): $ (CDR '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ (ТОПОЛЬ ЕЛЬ) Далее рассмотрим более сложную конструкцию, когда значение, возвращенное одним обращением к функции, используется в качестве аргумента для другого обращения. Примеры: 1. нахождения второго элемента списка (СТОЛ 423 СТУЛ) $ (CAR (CDR '(СТОЛ 423 СТУЛ))) $423 4 2. нахождение элемента СИНИЦА из списка $ ((ДЕРЕВО ЛИПА)(ПТИЦА СИНИЦА)(МЕБЕЛЬ СТОЛ)) $ (CAR (CDR (CAR (CDR '((ДЕРЕВО ЛИПА)(ПТИЦА СИНИЦА)(МЕБЕЛЬ СТОЛ)))))) $ СИНИЦА CAR и CDR названы селекторными функциями, потому, что они используются для выбора части объекта. Конструирующие функции выполняют дополнительную операцию построения составных объектов из более простых. Конструирующая функция CONS - одна из пяти примитивных функций "чистого" MuLISP. Она используется чтобы добавлять объекты впереди существующего списка. Первый параметр - оъбект, который нужно добавить, второй параметр - существующий список. Пример: $ (CONS 'СМЫСЛ '(ВСЕГО ПРЕДЛОЖЕНИЯ)) $ (СМЫСЛ ВСЕГО ПРЕДЛОЖЕНИЯ) Следует обратить внимание, что CAR результата, возвращенного CONS, будет всегда первый параметр для CONS. Также как CDR результата будет всегда второй параметр. Примеры: $ (CAR (CONS 'СЛОВО '(БУКВА СИМВОЛ))) $ СЛОВО $ (CDR (CONS 'СЛОВО '(БУКВА СИМВОЛ))) $ (БУКВА СИМВОЛ) При работе с MuLISP требуется различать, является ли объект списком или атомом. Для этого используется четвертая примитивная функция - АТОМ. Это функция с одним параметром. Если параметр атом, возвращается атом Т /истина/, а иначе - атом NIL /ложь/. Примеры: $(АТОМ '11) $Т $ (АТОМ '(ЛИПА ТОПОЛЬ ЕЛЬ)) $NIL Пустой список не является списком, это - атом NIL , то есть: $'0 Последняя примитивная функция чистого MuLISP - EQL. Это - функция компаратор, служащая для определения того, что два ее параметра-атома одинаковы. 5 Примеры: $ (EQL 5 5) $Т $ (EQL 3 3.0) $NIL Обратите внимание, что в MuLISP нет никакой необходимости заключать числа в скобки, хотя это приемлемо. Функция EQL используется только для сравнения параметров-атомов. Если в качестве параметров заданы списки (за исключением пустого списка, который всегда атом) EQL будет всегда возвращать NIL. Вывод: В этом уроке мы обсудили, что такое атомы и списки в MuLISP. Эти объекты используются вместе, чтобы составить символическую структуру данных, с которой функции MuLISP работают. "Чистый" MuLISP обеспечивает следующие пять примитивных функций: 1. (CAR <список>) - селекторная функция, которая возвращает первый элемент списка <список>; 2. (CDR <список>) - селекторная функция, которая возвращает все элементы списка <список>, кроме первого; 3. (CONS <объект> < список>) функция-конструктор, которая возвращает список, чей CAR - <объект> и чей CDR - <список>; 4. (EQL <атом1> <атом2>) функция компаратор, которая возвращает Т, если <атом1> и <атом2> одинаковы, иначе возвращает NIL ; 5. (АТОМ <объект>) функция-распознаватель, которая возвращает Т, если <объект> - атом, иначе возвращает NIL. Задание: 1. Выделите элемент, который не соответствует понятию "дерево" (БЕРЕЗА ЕЛЬ МАШИНА ТОПОЛЬ) 2. Найдите элемент 23 из списка: ((СЛОВО ОДНО)(БУКВА 23)(СИМВОЛ 11)) 3. Вычислите значения следующих выражений: a). (CONS (CAR '(a b))(CDR '(a b))) б). (CDR (CAR (CDR '(a b c)))) в). (CONS NIL NIL) 4. Какие из следующих вызовов возвращают значение Т: а). (АТОМ (+11 (-21 8))) б). (EQL 8.0 8) в). (АТОМ (АТОМ (+ 4 6))) г). (EQL 3.14 3.14) 6 УРОК № 2 На этом уроке мы рассмотрим, как добавлять свои собственные функции к списку уже созданных базовых функций. Подобно базовым функциям, функции, создаваемые пользователем, могут содержать аргументы и возвращать результат в зависимости от значений аргументов. Естественно, что перед вызовом функцию необходимо определить и только после этого можно вызывать и задавать ей аргументы. Следовательно, перед заданием значений аргументов необходимо в определении функции указать способ ссылки на них (имя). Имя, используемое в определении функции для обозначения еще неопределенного ДЕЙСТВИТЕЛЬНОГО АРГУМЕНТА, называется ФОРМАЛЬНЫМ АРГУМЕНТОМ. Список формальных аргументов функции указывается в начале определения функции. В "чистом" MuLISP выражения вида (DEFUN <name> (<argl> <arg2> ...) <taskl> <task2> ... ), используются для определения имени функции <name>. Атомы <argl>, <arg2> и т.д. являются именами формальных аргументов, используемых для обращения к действительным аргументам функции. Само тело функции состоит из одного или нескольких отображений (команд) <tasks>, которые должна выполнять функция. DEFUN означает: определить (Define) функцию (FUNction). Пример определения функции: Некоторые люди предпочитают мнемоническое имя для функции, возвращающей первый элемент списка, такое например как FIRST (Первый), чем CAR. Это очень легко разрешается следующим определением: $ (DEFUN FIRST (LST) (CAR LST)) Заметим, что: - имя определенной функции FIRST, - у нее один аргумент, - к ней можно обратиться, используя имя LST внутри тела функции, - тело функции содержит только одну команду, которая возвращает CAR от аргумента. Функции, определенные самим пользователем, вызываются так же, как и базовые функции. Пример: $ (FIRST '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ЛИПА 7 Когда вводите выражения, содержащие более одной строки, следите за правильностью их написания, так как после нажатия клавиши (ВВОД) уже нельзя будет редактировать предыдущие строки. В уроке №1 мы узнали, что атом NIL используется для обозначения пустого списка. NIL также возвращается, когда результатом функций EQL и АТОМ было значение ЛОЖЬ. Очевидно, что NIL является очень неординарным атомом MuLISP, так что необходимо уметь отличать его. Для этого используется функция NULL , возвращающая Т (true), если аргумент пустой список (т.е. NIL), или NIL, если аргумент не NIL. Пример: $ (DEFUN NULL (OBJ) (EQL OBJ NIL)) До сих пор, функции, определяемые пользователем, были всего навсего обобщением команд, выполняющих одно преобразование, которое и составляло тело функции. Но настоящая сила функций исходит от их возможности выбирать, в зависимости от значений аргументов, тот или иной путь. В данный момент в нашем распоряжении находятся три функции, которые можно использовать для проверки (сравнения) аргументов. Это EQL, ATOM и NULL. Функции, используемые для проверки аргументов часто называются ПРЕДИКАТАМИ (PREDICATES). Далее рассмотрим как употреблять предикаты для принятия решений внутри определений функций. Как говорилось раньше, тело функции может состоять из одной или нескольких команд. В "чистом" MuLISP команды (задания) можно подразделить на два класса: простые команды и условные команды. Команда, являющаяся атомом или списком, чей CAR (то есть первый элемент) - атом, является ПРОСТОЙ (SIMPLE) командой. Пример: (CONS ATM LST) Команда, являющаяся списком, чей CAR - HE атом, является УСЛОВНОЙ командой. Пример: ((ATOM EXPN)(CONS EXPN LST)) CAR условной команды - есть предикат условности. Если предикат возвращает NIL, то и значение команды также NIL, и процесс вычисления тела функции продолжается со следующей команды, если она есть. Если предикат возвращает значение отличное от нуля, то оставшиеся команды игнорируются, и процесс вычисления тела функции продолжается, используя CDR от условной команды. 8 Пример: У нас уже есть функция АТОМ, которая возвращает Т тогда и только тогда, когда ее аргумент - атом. Однако, в "чистом" MuLISP нет аналогичной базовой функции для списков. Потому что мы сами можем написать ее, если потребуется. Так как новая функция будет предикатом LIST, то назовем ее LISTP. Если ее аргумент атом, то она должна вернуть NIL. Если ее аргумент не атом, а список, то LISTP должна вернуть Т. $ (DEFUN LISTP (OBJ) ((ATOM OBJ) NIL) T) Другими словами тело данного определения значит: "Если OBJ - атом, то верни NIL, иначе верни Т". Теперь определим функцию LISTP так, чтобы она возвращала Т при пустом списке. $ (DEFUN LISTP (OBJ) ((NULL OBJ)) ((ATOM OBJ) NIL) T) Примеры использования функции LISTP: $ (LISTP '0 $T $ (LISTP '()) $T $ (LISTP '(ЛИПА ТОПОЛЬ ЕЛЬ)) $NIL Заметьте, что так как функция LISTP на NULL даст Т, то нет необходимости ставить Т после (NULL OBJ). Если вы создали длинный список имен, то вам наверняка потребуется определить, является ли какое-нибудь имя членом этого списка. Применяя последовательно функцию-сравнитель EQL, можно сравнить интересующее вас имя с каждым элементом списка. Пример: $ (DEFUN MBR (NAM LST) ((EQL NAM (FIRST LST))) ((EQL NAM (SECOND LST))) ((EQL NAM (THIRD LST))) ((EQL NAM (THIRD (CDR LST)))) 9 Такая форма записи не только постепенно становится все сложнее и сложнее, но к тому же ей нет конца. Тем более, мы надеялись, что наша новая функция MBR будет универсальной, то есть применимой к спискам произвольной длины. Так что попробуем подойти к этой проблеме с другой стороны. Если у вас есть имя и список, то можно сказать следующее: 1. Если список пуст (то есть у него нет элементов), то имя не является элементом списка. 2. Если имя EQL (равно) CAR списка, то имя - элемент данного списка. 3. Если имя не EQL CAR списка, то имя является элементом списка тогда и только тогда, когда оно является элементом CDR списка. Преобразуем данные три факта в трехступеньчатую процедуру, определяющую, является ли имя элементом списка или нет: 1. Если список - NULL, то возвратить NIL. 2. Если имя EQL CAR списка, то возвратить Т. 3. Иначе применить данную процедуру к CDR списка и вернуть ее результат. Это называется рекурсивным определением, т.к в третьем пункте мы предлагаем применить описанную в п. 2 процедуру, для определения наличия интересующего нас имени в CDR списка. Процедура может быть запросто преобразованна в определение рекурсивной функции: $ (DEFUN MBR (NAM LST) ((NULL LST) NIL) ((EQL NAM (CAR LST)) T) (MBR NAM (CDR LST))) В уроке № 1 уже говорилось, что EQL применима только лишь для сравнения атомов, так как если при помощи нее мы будем сравнивать списки, то на выходе получим NIL . Пример: $ (EQL '(ЛИПА ТОПОЛЬ ЕЛЬ) '(ЛИПА ТОПОЛЬ ЕЛЬ)) $NIL Применяя уже известный нам из примера функции MBR прием, определим функцию EQLIST, которая возвращает Т если два списка равны, или NIL если они не равны. 1. 2. 3. 4. Если первый список NULL, то возврат NULL во второй список. Если второй список NULL, то возврат NIL. Если CAR первого списка NOT EQL CAR второго, то вернуть NIL. Возврат CDR EQLIST первого списка в CDR второго. NOT - функция-предикат, возвращающая Т, если ее единственным аргументом является NIL). Пример определения EQLIST и NOT: $ (DEFUN EQLIST (LST1 LST2) ((NULL LST1)(NULL LST2)) ((NULL LST2) NIL) 10 ((NOT (EQL (CAR LST1)(CAR LST2))) NIL) (EQLIST (CDR LST1)(CDR LST2))) Функция NOT по определению напоминает ранее рассмотренную функцию NULL. Это связано с тем, что атом NIL в диалекте MuLISP имеет двоякий характер: он может служить в роли обозначения пустой строки или быть в роли логической константы равной значению "Ложь". До сих пор все определенные нами функции были либо предикатами, либо в той или иной степени фильтрами. Далее мы попробуем определить функцию, отображающую несколько множеств в одно. В частности, функцию, объединяющую два списка. Попробуем вызвать функцию CONS с двумя списками в качестве аргументов: $ (CONS '(Ивановы Иван Иванович) '( и Анна Ивановна)) $ ((Ивановы Иван Иванович) Анна Ивановна) Ответ (список) вместо того, чтобы состоять из шести элементов, будет включать в себя только четыре элемента, первый из которых будет списком из трех элементов. Для получения же желаемого результата необходимо применить функцию CONS следующим образом: $ (CONS 'Ивановы (CONS 'Иван (CONS 'Иванович)'( и Анна Ивановна))) $ (Ивановы Иван Иванович и Анна Ивановна) Хотя многократные применения CONS и дают желаемый результат, но нам нужна одна универсальная функция, которая возвращала бы результат, имея в качестве своих аргументов строки произвольной длины. Обратите внимание на сложность определения функции APPEND с помощью пяти базовых функций MuLISP и функций, определяемых пользователем, которые уже должны будут быть определены к моменту вызова функции APPEND. He забывайте, APPEND также квалифицируется как "функция, определенная пользователем, и которая также будет определена к моменту вызова APPEND" (другими словами APPEND может быть определена рекурсивно). Начнем, разбив нашу задачу на две менее сложные. Если даны два списка LST1 HLST2, то 1. если LST1 пуст, вернуть LST2; 2. иначе применить функцию CONS к CAR LST1 и к списку, полученному после применения функции APPEND к CDR LST1 и LST2. $ (DEFUN APPEND (LST1 LST2) ((NULLLST1)LST2) (CONS (CAR LST1)(APPEND (CDR LST1) LST2))) Пример: $ (APPEND '(Ивановы Иван Иванович) '(и Анна Ивановна)) $ (Ивановы Иван Иванович и Анна Ивановна) 11 Чтобы понять данное рекурсивное определение, рассмотрите отдельно два аргумента CONS в последней строке определения. Первый аргумент (CDR LST1) -это просто первый элемент LST1. В выше приведенном примере первый аргумент CONS атом Ивановы. Полагая, что APPEND работает правильно, второй аргумент функции CONS (APPEND (CDR LST1) LST2), список, полученный после объединения CDR списка LST1 со списком LST2. Заметьте, что это приемлемая форма рекурсии, так как каждый раз когда функция APPEND вызывает себя, аргумент LST1 все короче и короче, что по достижению LST1 значения пустой строки обеспечит выход из рекурсии. В выше приведенном примере, второй аргумент CONS - список (Иван Иванович и Анна Ивановна). Последняя функция, которую мы рассмотрим в уроке № 2, - это функция REVERSE, относящаяся к классу функций обработки списков. Ее смысл прост -она переставляет элементы списка в обратном порядке (тот, который был первым, стал последним, а последний - первым). Пример: $ (REVERSE '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ (ЕЛЬ ТОПОЛЬ ЛИПА) Но реализация функции не очевидна. Для определения функции REVERSE кроме пяти основных функций можно свободно пользоваться уже ранее определенными функциями, в том числе и APPEND. Применяя рекурсию, можно применить REVERSE к CDR LST следующим образом: $ (REVERSE (CDR LST)) После чего применить REVERSE ко всему списку LST. Все, что теперь остается сделать, так это применить APPEND к "REVERSE CDR LST" и к одноэлементному списку: $ (CONS (CAR LST) NIL) Пример определения функции REVERSE: $ (DEFUN REVERSE (LST) ((NULL LST) NIL) (APPEND (REVERSE (CDR LST)) (CONS (CAR LST) NIL))) Пример использования функции REVERSE: $ (REVERSE '(СЛОВО СИМОЛ БУКВА)) $ (БУКВА СИМВОЛ СЛОВО) Хотя это и вполне логически приемлемое определение REVERSE, оно крайне нерационально. Так как каждый раз, когда нам нужно переставить 12 элемент, мы вызываем функцию APPEND, а число перестановок равно числу элементов списка. Давайте попробуем новый подход в наших условиях по более эффективному определению функции REVERSE. Самый простой способ повернуть пачку бумаги - брать один за другим верхние листы (то есть CAR) стопки и класть их во вторую стопку, пока первая не будет взята вся. Когда мы начинаем этот процесс, во второй стопке не должно быть никаких листов. Если у нас даны список и пустой список, то вышесказанное можно записать в виде рекурсивной процедуры, инвертирующей (REVERSE) первый список: 1. Если первый список NULL, возвратить второй список. 2. Иначе объединяем при помощи функции CONS CAR первого списка и второй список, а затем применяем REVERSE к CDR первого списка. Действуя по намеченному плану, вы вполне можете сейчас определить данную "рациональную" версию функции REVERSE $ (DEFUN REVERSE (LST1 LST2) ((NULLLST1)LST2) (REVERSE (CDR LST1) (CONS (CAR LST1) LST2 ))) Пример использования: $ (REVERSE '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ (ЕЛЬ ТОПОЛЬ ЛИПА) Заметьте, что хотя REVERSE по описанию содержит ДВА формальных аргумента, при вызове был указан только ОДИН дествительный аргумент. В общем случае лишним формальным аргументам присваивается значение NIL (это часто бывает удобно). В этом уроке вы познакомились со способами расширения "чистого" MuLISP, путем определения новых функций. Вывод: 1. Определение включает в себя: имя функции, список формальных аргументов и команды, образующие тело функции. 2. Существует два вида команд: простые и условные. В зависимости от значения, возвращенного функцией-предикатом, условные команды используются для принятия решений, при оценке функций. 3. Рекурсивное определение функций - это мощный и наглядный аппарат. Рекурсивное определение функций допустимо только тогда, когда аргументы по своим значениям близки к значениям, обеспечи- вающим выход из рекурсии. Окончание нашего разговора о "чистом" MuLISP не говорит о том, что исчерпан потенциал всевозможных функций, которые могут быть написаны на этом уровне MuLISP. 13 Задание: Определите следующие функции: 1. (EQUAL <listl> < list2>) - сравнивает два списка <listl> и <list2>, но в отличие от функции EQLIST , она выполняет сравнение, даже если элементы списка - не атомы. Попробуйте данную функцию, например, на таком списке ((А В (С D)) (Е F)). 2. (SUPER-REVERSE <list>) инвертирует все уровни списков списка <list>. Например, список ((А В(С D)) (E F)) преобразует в список((F Е ) ((D С) В А)). 3. (UNION <setl> <set2>) возвращает сет-теоретическое объединение <setl> и <set2>. (Сеты (наборы) - это списки, в которых значения всех элементов разные). 4. (INTERSECTION <setl> <set2>) возвращает пересечение двух сетов. 5. (SUBST <new> <old> <list>) заменяет упоминание <old> /старые/ на <new> /новые/ в списке <list>. 14 УРОК № 3 Первые два урока обучали основным правилам программирова-ния на MuLISP, с использованием "чистого" MuLISP. Все, что вы узнали относительно "чистого" MuLISP, конечно, действительно и для полного MuLISP; однако, можно еще много сказать. Этот урок посвящен дальнейшему описанию возможностей MuLISP. Существует три типа объектов данных в MuLISP: символы, числа и списки. В уроке № 3 мы будем исследовать каждый из трех примитивных объектов данных MuLISP в деталях. Этот урок является информативным, однако, он обеспечивает существенную основу для следующих уроков. MuLISP обеспечивает многочисленные функции распознавания, сравнения, объединения, и действия над этими примитивными объектами данных. Это позволяет вам создавать комплексные структуры данных, которые могут точно моделировать в ЭВМ фактически любую реальную проблему. Следовательно, мы начинаем наше обсуждение MuLISP с описания трех базисных объектов данных. Как вы помните, "чистый" MuLISP имеет только два типа объектов данных: атомы и списки. В полном MuLISP атомы далее подразделены на символы и числа. В полном MuLISP списки - только подмножество более общих структур, называемых двоичными деревьями, которые составлены из списков. Первый тип объекта данных, который мы будем обсуждать - символ. С каждым символом MuLISP связывает 4 атрибута: 1. Имя печати символа: неповторяющаяся строка символов ASCII , используемых системой для идентификации символа при вводе и отображении символа при выводе. Строка имени печати символа не может изменяться (заменяться). 2. Текущее значение: значением символа может быть любой объект данных MuLISP, включая свое собственное значение. Значение символа по умолчанию -сам символ. 3. Список свойств: он содержит значения символьных свойств, индексно обозначенных ключами. См. MuLISP урок о значениях свойств относительно их использования. Список свойств - нулевой по умолчанию. 4. Функциональное описание: функциональное описание символа применяется к аргументам, когда символ вызывается как функция. По умолчанию, функциональное описание вызывает прерывание и выдается сообщение "Неопределенная функция". Функция распознавания SYMBOLP возвращает Т , если одиночный параметр символ; иначе он возвращает NIL. Примеры: $ (SYMBOLP 'ABC) $Т $ (SYMBOLP 20) $Т 15 $ (SYMBOLP '(один два три)) $NIL Как обсуждалось в предыдущих уроках, функция сравнения EQL используется для определения, являются ли два символа одинаковыми. Пример: $ (EQL 'СТУЛ (CAR '(СТУЛ СТОЛ ОКНО))) $т Так как пустые пробелы, круглые скобки и некоторые другие специальные символы имеют специальное применение в MuLISP, цитируемая строка должна использоваться, чтобы создать символ с такими символами в строке имени печати. Для создания такого символа просто включить строку, содержащую такие символы в пределах двойных кавычек. Пример: $ "Это слово (символ) не верен!" Обычно двойные кавычки вокруг специальных символов не отображаются, когда символ выходной. (Примечание: двойные кавычки автоматически отображаются вокруг таких символов, если значение переменной управления PRIN1 NIL.) Пустая строка, введенная как "", также является символом. Пример: $ (SYMBOLP "") $Т Двойная кавычка также может включаться в строку имени печати при вводе наклонной левой черты и метки кавычки на месте каждой желательной кавычки в строке. Пример: $ "Этим \"зверем\" оказалась мышка." При вводе символов, использующих двойные кавычки, важно внимательно за ними следить. После ввода метки кавычки MuLISP будет продолжать думать, что вы вводите одиночный символ, пока не считается метка кавычки соответствия. Символу можно присваивать значение. Значение может быть любым объектом данных MuLISP, включая свое собственное. По умолчанию, значения большинства символов MuLISP равны их собственным значениям. SET - примитивная функция, используемая для присваивания символу (ПЕРВЫЙ параметр) значения (ВТОРОЙ параметр). Следующий по важности факт SET возвращает второй параметр как значение. 16 Пример: $ (SET 'ПТИЦЫ '(СИНИЦА ГОЛУБЬ ОРЕЛ)) $ ПТИЦЫ Вообще первый параметр SET будет символ с апострофом. Вместо постановки апострофа перед символом можно использовать функцию SETQ (SET Quote). SETQ автоматически ставит апостроф перед первым параметром, но не перед вторым. Пример: $ (SETQ ФУНКЦИИ '(CAR CDR CONS ATOM EQ)) $ (CAR CDR CONS ATOM EQ) Второй тип объекта данных MuLISP, который мы будем обсуждать - числа. Числа далее подразделены на целые и дробные. Целые числа вводятся как непрерывный ряд цифр со знаком или без него. Так как значение числа - само число, не надо ставить перед ними апостроф. Примеры: $ 12 $-23 Функция сравнения EQL используется для проверки равенства двух чисел: Примеры: $ (EQL 6 7) $NIL $ (EQL 0 -0) $Т Дроби могут вводиться с использованием десятичных чисел или косой черты (то есть как два ряда цифр, отделяемых десятичной точкой или символом косой черты, со знаком минуса или без него). По умолчанию дроби отображаются с использованием десятичного представления. Примеры: $6/7 $-0.25 $ (EQL 0.8 1/6) $NIL Если значение управляющей переменной *POINT* - NIL, MuLISP отображает дроби, используя косую черту, вместо десятичного представления. Обратите внимание, что дроби автоматически сокращаются. Также дроби со знаменателем, кратным числителю, автоматически преобразуются в целые числа. 17 Примеры: $ (SETQ*POINT*NIL) $-6/7 $ 0.333333 $ 12/9 $ 5/1 Если значение управляющей переменной *POINT* - нуль или положительное число, MuLISP отображает дроби в десятичном представлении с максимальным количеством разрядов. Примеры: $ (SETQ*POINT*3) $2/3 $ (SETQ*POINT*7) $2/3 Примитивная функция распознавания INTEGERP возвращает Т , если параметр - целое число; иначе она возвращает NIL. Примеры: $ (INTEGERP 33) $Т $ (INTEGERP 0.23) $NIL Примитивная функция распознавания NUMBERP возвращает Т, если параметр число (то есть или целое число, или дробь); иначе она возвращает NIL. Примеры: $ (NUMBERP 33) $Т $ (NUMBERP 22.55) $Т $ (NUMBERP - F/C) $NIL Используя NUMBERP и SYMBOLP можно выяснить, что последовательность цифр заключенных в двойные кавычки (например "567"), является символом, а не числом: $ (NUMBER "567") $NIL $ (SYMBOLP "567") $Т 18 Символы и числа - вместе называются атомы, чтобы описать их индивидуальность обычными средствами. Примитивная функция распознавания АТОМ возвращает Т , если параметр - атом (то есть символ или число); иначе -NIL. Примеры: $ (АТОМ 'С) $Т $ (АТОМ '(СПИСОК СЛОВ)) $NIL $ (ATOM NIL) $Т $ (АТОМ (+5 4)) $Т $ (ATOM '(NIL)) $NIL Иногда нужно обратиться к символу, а не к его значению. В этом случае в MuLISP символ апостроф используется как метка кавычки для обращения к символу. Третий примитивный объект данных - Списки Пример: Адрес 1 2 3 20 Данные 4 5 7 2 6 7 ИВАНОВ Предположим что мы хотим представить список, состоящий из символа ИВАНОВ и его возраста 20. Символ ИВАНОВ можно сохранить начиная с позиции 7, его возраст 20 с позиции 2 и, начиная с позиции 4, сохранить список, из пары адресов 7 и 2. MuLISP автоматически управляет специфическим размещением данных в памяти, таким образом все, о чем мы заботимся, - специфические примитивные символы и числа, и связь между ними. MuLISP хранит пути адресов типа 7 и 2 в нашем примере, но для нас они - абстракция. Представление подробность:' "box-car" ИВАНОВ списков 20 подавляет такую несоответствующую 19 Впредь, для уменьшения помех, мы будем использовать следующий способ записи: ИВАНОВ 20 Так как каждый список имеет точно две "ветви", такие диаграммы называются двоичными деревьями. Хотя редактируемая совокупность списков лучше всего воспринимается людьми как двоичная структура дерева, линейное представление списков более подходит для языка программирования. Одно из линейных представлений, употребляемых MuLISP - так называемое ТОЧЕЧНОЕ представление. Здесь список - представленный левой круглой скобкой элемент данных, период (точка) и элемент данных, представленный правой круглой скобкой. Пример: ИВАНОВ 20 Представляется в ТОЧЕЧНОМ виде: (ИВАНОВ.20) Левый элемент списка называется CAR списка. Правый элемент называется CDR списка. Элементами списка могут быть любые объекты данных, включая другой список. Редактирование списков может использоваться для представления фактически любого дерева структуры данных. Часто естественней представлять совокупность списков как СПИСОК объектов данных, чем как глубоко вложенное двоичное дерево. Например, элементы набора обычно отображаются как список. MuLISP представляет список как редактируемую совокупность списков, чьи CAR ячейки указывают на элементы списков и чьи ячейки CDR указывают на следующий список. Список связей завершается ячейкой CDR, которая указывает на NIL. Пример: object 1 object 2 objectN NIL Если это двоичное дерево повернуть на 45 градусов против часовой стрелки, проще увидеть, почему это используется для представления списков объектов данных: 20 NIL object 1 object2 objectN Линейная структура списков предполагает внешнее печатное представление, которое является еще более читаемым, чем эквивалентное точечное представление. Таким образом MuLISP будет автоматически отображать вышеупомянутое объектное использование представления СПИСКА: (Object 1 object2 ... objectN) Скорее, чем использование точечного представления: (Object 1. (Object2...(objectN. НУЛЬ)...)) Представление списка в MuLISP обычно происходит в линейном представлении, или в точечном, если необходимо. Таким образом, структура формы object 1 object 2 objectN atom где <atom>, не символ NIL, отображается в смешанном представлении как: (Iteml item2 ... itemN. Atom) MuLISP поддерживает линейное, точечное и смешанное представление. Кроме того, любой из элементов списка может сам быть любым списком или более общим выражением. Вывод: В течении всего урока мы описывали три примитивных объекта данных MuLISP - символы, числа и списки. Следует запомнить: 1. Символы: каждый символ имеет связанную строку печати, значение, список свойств и функциональное описание. Символы, содержащие специальные символы, могут вводиться с использованием строки в кавычках. SETQ - функция, обычно используемая, чтобы присваивать значение символу. 2. Числа: число - положительный или отрицательный рациональный номер. NUMBERP используется чтобы распознавать числа. Числа подразделены на 21 целые числа или дроби. INTEGERP используется чтобы распознавать целые числа. 3. Списки: списки используются чтобы формировать двоичные структуры (деревья), которые могут представляться с использованием точечного, линейного или смешанного представления. 22 УРОК № 4 В этом уроке мы рассмотрим следующие вопросы: 1. Селектор-функции, используемые для выбора компонентной части бинарного дерева структурного информационного объекта. 2. Функции-конструкторы, используемые для формирования бинарных деревьев из более простых информационных объектов. 3. Итерация против рекурсии. Когда мы говорили о "чистом" MuLISP, мы рассматривали функции CAR и CDR с точки зрения применения их для списков. CAR списка - это первый элемент списка. CDR списка - это все кроме первого элемента списка. Так как списки всего лишь своеобразный способ представления бинарных деревьев, то соответственно функции CAR и CDR можно применять и к бинарным деревьям. Как уже говорилось в прошлом уроке, левая ветвь дерева называется CAR ветвь, а правая CDR -ветвь. Таким образом, функции CAR и CDR соответственно расширяют левую и правую ветви бинарного дерева. Примеры: $ (CAR '(зеленый.синий)) $ (CDR '(зеленый.синий)) Кроме CAR и CDR, в MuLISP уже определены различные сочетания CAR и CDR. Имена данных функций (сочетаний) формируются засчет средних букв CAR и CDR , которые указывают на соответствующие преобразования. Например, функция CADR означает взять CAR от CDR-a списка. Примеры: $ (CAR (CDR '(ЛИПА ТОПОЛЬ ЕЛЬ))) $ ТОПОЛЬ $ (CADR '(ЛИПА ТОПОЛЬ ЕЛЬ)) $ ТОПОЛЬ Другие различные сочетания из двух, трех, четырех вызовов CAR и CDR (например, CDAR, CAAR, CDDR, CAAAR, CAADR, CADAR, CDAAR и т.д.) также уже определены в MuLISP. Эти функции, во-первых, намного эффективнее, чем комбинация CAR и CDR, а, во-вторых, приходится меньше печатать. Пример опаределения функций SECOND функций-сочетаний: $ (DEFUN SECOND (LST) и THIRD, с определением 23 (CADR LST)) $ (DEFUN THIRD (LST) (CADDR LST)) Пример использования: $ (SECOND '(ЛИПА ТОПОЛЬ ЕЛЬ БЕРЕЗА)) $ ТОПОЛЬ До сих пор мы обращались к списку, начиная с левого конца. Но иногда возникает потребность обратиться к последнему элементу списка. Пример рекурсивного определения функции LAST-ELEMENT, которая возвращает последний элемент списка. Заметьте, что случай, когда LST пустая строка необходимо оговорить отдельно: $ (DEFUN LAST-ELEMENT (LST) ((NULL LST) NIL) ((NULL (CDR LST)) (CAR LST)) (LAST-ELEMENT (CDR LST))) $ (LAST-ELEMENT '(ПРЕДЛОЖЕНИЕ СЛОВО БУКВА СИМВОЛ )) $ СИМВОЛ До сих пор речь шла только о ФУНКЦИОНАЛЬНОМ (APPLICATIVE) стиле программирования. Данный стиль главным образом ориентируется на сравнении выражений, функциональную композицию и рекурсию. MuLISP также поддерживает ИТЕРАЦИОННЫЙ (INTERATIVE) стиль программирования. Данный стиль главным образом ориентируется на итерационное управление структурами и последовательную обработку. Функция LOOP является основной итерационной конструкцией MuLISP. LOOP может иметь любое количество аргументов, которые последовательно сравниваются, подобно заданиям (выражениям) в теле функции, за исключением того, что: 1). после того, как произойдет сравнение последнего аргумента LOOP-a, управление возвращается к первому заданию (выражению) в цикле; 2). когда в цикле возникает условное выражение, неравное NIL, значение этого выражения возвращается как результат работы цикла, и начинается сравнение следующего за циклом условного выражения, если такое существует. Внутри цикла может быть любое количество условных выходов из него. Если подобных выходов не существует, то цикл будет продолжаться до бесконечности. Для того, чтобы проиллюстрировать работу LOOP приведем альтернативный вариант рекурсивному определению функции LAST-ELEMENT: $ (DEFUN LAST-ELEMENT (LST) ((NULL LST) NIL) (LOOP ((NULL (CDR LST)) 24 (CAR LST)) (SETQ LST (CDR LST)))) $ (LAST-ELEMENT '(ПРЕДЛОЖЕНИЕ СЛОВО СИМВОЛ БУКВА)) $ БУКВА Итерационная функция MBR с употреблением LOOP. (MBR atom list) возвращает Т если <atom> EQL любому элементу списка <list>; иначе возвращает NIL. Пример определения функции MBR: $ (DEFUN MBR (ATM LST) (LOOP ((NULL LST) NIL) ((EQL ATM (CAR LST))) (SETQ LST (CDR LST)))) $ (MBR СЛОН '(ВОЛК ДЯТЕЛ КОРОВА СЛОН ОРЕЛ)) $Т В MuLISP есть уже определенная (стандартная) функция для доступа к правому концу списка - LAST. Однако, в отличие от функции LAST-ELEMENT, LAST возвращает последний cons списка, а не последний элемент. Стандартная функция NTH полезна для извлечения п-го элемента из списка. Если <п> - неотрицательное целое, то (NTH n list) возвратит <п> -ый элемент списка <list>. В MuLISP используется ZERO BASED INDEXING, то есть индексация элементов с нуля. Поэтому первый элемент (то есть саг) списка значится под номером "О". Примеры: $ (NTH 0 '(ваш номер в списке)) $ ваш $ (NTH 2 '(a b с d e)) $Ъ С помощью NTH можно определить функцию INDEXER такую, что если <list2> является списком чисел, то (INDEXER list l Hst2) возвращает список, состоящий из элементов списка <listl>, с порядковыми номерами равными соответствующим элементам списка <list2>. $ (DEFUN INDEXER (LST1 LST2) ((NULL LST2) nil) (CONS (NTH (CAR LST2) LST1)(INDEXER LST1 (CDR LST2)))) Примеры использования: $ (INDEXER '(А В С D E F G) '(6 2 4 0)) $ (G С Е A) 25 $ (INDEXER '(ЕЛЬ ЛИПА ТОПОЛЬ БЕРЕЗА) '(3 1 2)) $ (БЕРЕЗА ЛИПА ТОПОЛЬ) Если NTH возвращает элемент списка, то стандартно определенная функция NTHCDR возвращает хвост списка. Если <п> является неотрицательным числом, то (NTHCDR n list) возвращает <п>-ый cdr списка <list>. Пример использования: $ (NTHCDR О '(ВОТ ВАШ ПРИМЕР)) $ (ВАШ ПРИМЕР) Причем, как NTH, так и NTHCDR возвпащают NIL , если <п> больше числа элементов списка. Теперь мы рассмотрим стандартные функции-конструкторы MuLISP. При изучении "чистого" MuLISP мы рассматривали функцию CONS как функцию, используемую для построения списков. CONS также может быть рассмотрена как функция, используемая для формирования бинарных деревьев. CONS образует один cons, CAR которой является первым аргументом для CONS, a CDR вторым. Предположим, что нам нужно сделать список значений, приписанных к следующим переменным: Фамилия, Имя, Адрес. Предаоложим, что они имеют следующие значения: $ (SETQ Фамилия 'Морозов) $ (SETQ Имя 'Сергей) $ (SETQ Адрес 'Садовая, 19), то при многократном применении функции CONS, мы могли бы получить интересующий нас список следующим образом: $ (CONS Фамилия (CONS Имя (CONS Адрес NIL))) Вместо того, чтобы вызывать CONS для каждой переменной, можно просто использовать готовую стандартную функцию LIST. Эта функция может иметь неограниченное число аргументов и возвращать список своих аргументов. Пример: $ (LIST Фамилия Имя Адрес) $ (Фамилия Имя Адрес) Хотя мы сами определяли функцию APPEND, она в принципе уже существует, как стандартная функция MuLISP. И она универсальнее, чем определение, выполненное нами, так как может складывать любое число списков. Рассмотренные нами три стандартные функции- конструкторы: CONS, LIST, APPEND на первый взгляд похожи, но на самом деле каждая из них имеет свои особенности. Проверим это на примере, вызывая их с одинаковыми аргументами: 26 $ (CONS '(А В) '(С D)) $ ((A B)C D) $ (LIST '(А В) '(С D)) $ ((A B)(C D)) $ (APPEND '(А В) '(С D)) $ (А В С D) При изучении "чистого" MuLISP, мы определили функцию REVERSE, просто при помощи переменной, выступающей в роли сумматора. В результате наше оперделение выглядело следующим образом: $ (DEFUN REVERSE (LST1 LST2) ((NULLLST1)LST2) (REVERSE (CDR LSTl)(CONS (CAR LST1) LST2))) Пример использования: $ (REVERSE '(А В С D E)) $ (E D С В A) Можно применить тот же метод, с использование переменной в роли сумматора. $ (DEFUN REVERSE (LST1 LST2) (LOOP ((NULLLST1)LST20 (SETQ LST2 (CONS (CAR LST1) LST2)) (SETQ LST1 (CDR LST1)))) Пример использования: $ (REVERSE '(А В С D E )) $ (E D С В A) Часто при последовательной обработке элементов списка в цикле, мы применяем функцию CAR к нашему списку, а после укорачиваем наш список на один элемент. Данная операция аналогична снятию со стека ("popping" ) (то есть снятию первого элемента с вершины стека). Стандартно определяемая функция MuLISP POP выполняет подобные действия. Если <stack> является символом, чье значение список, то (POP stack) возвратит CAR списка и присвоит <stack> значение CDR списка. Пример: (SETQ LST2 (CONS (CAR LST1) LST2)) (SETQ LST1 (CDR LST1)) при помощи POP могут быть сведены в одно (SETQ LST2 (CONS (POP LST1) LST2)) 27 Другая, часто используемая при формировании списков внутри цикла, процедура заключается в помещении, при помощи обращения к функции CONS, объекта в начало списка. Это делается следующим образом: (SETQ stack (CONS object stack)) Стандартная функция PUSH может значительно сократить запись (PUSH object stack) Пример определения функции REVERSE, с помощью функций PUSH и POP: $ (DEFUN REVERSE (LST1 LST2) (LOOP ((NULLLST1)LST2) (PUSH (POP LST1) LST2))) $ (REVERSE '(А В С D E )) $ (E D С В A) В MuLISP уже существует стандартная функция REVERSE, которая эффективнее ранее определенных. После того как она завершает работу и готова определить результат MuLISP автоматически восстанавливает среду, которая была при вызове функции. Это означает, что вы можете изменить значения формальных аргументов функции, не беспокоясь за сохранение предыдущих значений формальных аргументов. По этим причинам функции можно рассматривать как "черные ящики", которые не оказывают абсолютно никакого влияния на окружающую среду, кроме как возвращаемый ими реультат. Если возникает необходимость в том, чтобы функция возвращала более, чем одно значение, она может создать и возвратить список значений. Другой способ с помощью которого функция может повлиять на внешнюю среду, это сделать присвоения аргументам, не включенных в список формальных параметров, внутри тела программы. Подобные переменные называются "специальными", "текущими" или "глобальными" переменными. Недостатком данного способа является то, что при модификации программы очень легко ошибиться и не заметить спрятанных каналов связи. Как рекурсивное, так и итерационное определение функции REVERSE занимают одинаковое время, пропорциональное длине их аргумента. Но для длинных списков итерационный вариант примерно на 20 % быстрее, в зависимости от компьютера и количества свободной памяти. Хотя рекурсивный вариант намного компактней. При выборе между скоростью и компактностью следует поступать следующим образом: если это часто употребляемые функции, то программировать ориентируясь на скорость, а во всех остальных случаях ориентироваться на краткость записи. Еще один фактор, который следует учесть при выборе между итерацией и рекурсией, это память, необходимая для выполнения данной задачи. Каждый раз при вызове функции, адреса возврата и предыдущие значения формальных аргументов должны быть сохранены на стеке, для того, чтобы можно было восстановить старую среду, при завершении работы функции. Так как рекурсии постоянно (с каждым вызовом) насаждает вызов функции, то не исключено, что функция может использовать всю доступную память и не 28 завершить свою работу. Применение итерации в такой ситуации может обеспечить продолжение вычислений до полного завершения. В MuLISP есть три стандартные логические функции, сделанные для объединения логических переменных, возвращаемых функциями -предикатами. Функция OR может содержать неограниченное число аргументов и сравнивает их слева на право до тех пор пока не наткнется на значение не равное NIL или пока не останется больше аргументов. Если будет найден аргумент неравный NIL , то OR вернет значение этого аргумента; иначе OR вернет NIL. При программировании можно положиться на то, что OR не сравнивает аргументы, стоящие после аргумента со значением не равным NIL. He NIL не означает Т, так что возвращаемый результат ограничен только двумя значениями Т или NIL. В целях управления программой MuLISP рассматривает всякое значение не равное NIL как Т. Данный факт является огромным удобством при программировании. Помните, что в MuLISP атом - это либо символ, либо число. То есть функция разделитель может быть определена следующим образом: $ (DEFUN ATOM (U) (OR (SYMBOLP U) (NUMBERP U))) Подобно OR, существует встроенная стандартная функция AND, которая может иметь любое количество аргументов. AND последовательно сравнивает свои аргументы пока не встретится NIL или пока не останется больше аргументов. AND возвращает значение последнего аргумента, подлежащего сравнению. Таким образом можно положиться на то, что после NIL далее стоящие аргументы не будут подлежать сравнению. Пример: $ (AND (SYMBOLP Trog)(NUMBERP 7)) Станадртно определенная функция NOT возвращает Т , если его аргумент является NIL ; иначе же возвращается NIL . Пример: $ (NOT (ATOM '(ПРИМЕР))) $т Как говорилось раньше, определение функции NOT идентично определению NULL. NULL следует использовать для проверки на пустые списки. A NOT следует использовать при проверке логического значения, возвращаемого функциямипредикатами. Вывод: 1. Применение 28 стандартно определенных функций CAR и CDR (CADR, CDDR, CAAAR и т.д.) для извлечения различных компонентов бинарного дерева. 2. Применение функции NTH и NTHCDR для доступа к элементам списка по их индексу. 29 3. Применение и различия трех стандартных функций-конструкторов CONS, LIST и APPEND. 4. Итерационное программирование, с употреблением конструкции LOOP, и функции работы со стеком, то есть PUSH и POP. 5. Логические функции AND, OR и NOT, которые используются для логического объединения логических переменных, возвращаемых функциями-предикатами. Задание: Вычислите значения следующих выражений: 1. (CADR '(самое простое описание понятия)) 2. (CADDR '((a b c)(d e)(f))) 3. (CADAR (CONS '(a b) nil)) 4. (NTH 2 '(1 2 3)) 5. (AND (AQTOM NIL) (+2 3)) 30 УРОК № 5 Этот урок представляет примитивные числовые функции языка MuLISP и некоторые возможности, полезные для записи эффективных математических функций. MuLISP обеспечивает пользователя и неограниченной точностью целой арифметики и, определяемой пользователем, точностью рациональной арифметики. Это значит, что единственное ограничение размера целых чисел -доступная память ЭВМ. Целые числа, состоящие из тысяч цифр, возможны. Это делает MuLISP полезным в исследованиях в области теории чисел и криптографии. По умолчанию MuLISP для рациональной арифметики обеспечивает приблизительно 7 цифр точности. Это аппроксимирует точность арифметики с плавающей запятой. Описание функции PRECISION в Главе 5 Справочного описания MuLISP подробно описывает увеличение точности рациональной арифметики. Это умножение и деление соответственно. Во многих стандартных языках программирования вы вводите числовые выражения использующие следующе представление: операторы ставятся между операндами. Пример: 2*3-1 Так как LISP - функциональный язык программирования, функциональное представление наиболее естественно и непротиворечиво для числовых выражений. Пример: вышеупомянутое выражение вводится так: $ (- (* 2 3) 1) Одно из приемуществ функционального представления перед операторным состоит в том, что вы не ограничиваетесь двумя операндами для каждой операции. Функции +, *, -, / могут действовать для переменного числа параметров. Пример: $(+ 12-19 34) $ (* 1 2 3 4 5) Если числовая функция случайно вызывается с нечисловым параметром, то на экране появится сообщение об ошибке: "Nonnumeric Argument", сопровождаемое обращением к функции обработки ошибок и подсказкой: Abort, Break, Continue, Top-level, Restart, Sistem? Тогда вы должны выбрать одну из пяти опций посредством ввода первого символа. Подробное описание функции BREAK находится в Главе 5 Справочного описания MuLISP. 31 Описание ошибок: Abort: аварийное прекращение работы, возвращение прямо к текущему уровню цикла read-eval-print и предложение пользователю дальнейшего ввода. Break: ввременно приостанавливает выполнение и запрашивает пользователя о вводе. Функция обработки ошибок может определяться при исследовании значения переменной BREAK. Для возврата напечатайте (RETURN expn), где <ехрп> - значение, которое будет возвращено как значение функции обработки ошибок. Continue: продолжает выполнение с использованием возвращенного значения функции обработки ошибок. Top-level: аварийно прекращает работу, возвращает управление верхнему уровню цикла read-eval-print, и предлагает пользователю произвести дальнейший ввод. Важной функцией является вычисление факториала. Факториал неотрицательное целое число N, обозначается N ! и может быть определен рекурсивно следующим образом: 0! = 1, N ! = N * ( N - 1 ) ! Если N>0 ) Эквивалентное описание MuLISP FACTORIAL: $ (DEFUN FACTORIAL (N) ((EQL N 0) 1) (* N (FACTORIAL (-N 1)))) Пример использования: $ (FACTORIAL 3) $6 Это эффективное описание; однако, если N больше, чем позволяет пространство памяти возникает ошибка, из-за переполнения стека. Итерационное описание для FACTORIAL: $ (DEFUN FACTORIAL (N % Local: % RSLT) (SETQRSLT 1) (LOOP ((ZEROP N) RSLT) (SETQ RSLT (* N RSLT)) (SETQN(-N 1))))MuLISP может обрабатывать очень большие числа. В описании показана примитивная программа - распознавателя ZEROP . (ZEROP N) эквивалентно (EQL N 0), но более эффективно. 32 Ряды Фибоначчи продолжают встречаться в природе в самых неожиданных местах. N номер Фибоначчи, обозначенный F(N), может быть определен рекурсивно следующим образом: F(0)=1 F(l)=l F(N)=F(N-1) + F(N-2), если N>1. Эквивалентное описание MuLISP FIBONACCI: $ (DEFUN FIBONACCI (N) ((ZEROPN) 1) ((EQLNl)l) (+ (FIBONACCI (-N l))(FIBONACCI (-N 2)))) Пример использования: $ (FIBONACCI 10) Из примеров видно, что этот алгоритм неэффективен. Проблема состоит в том, что для вычисления N числа Фибоначчи, (N - 2)-ое число Фибоначчи необязательно вычислять дважды, (N-3)-ee - трижды, (N-4)-oe - пять раз, и дальше еще хуже. Так как это - рекурсивный алгоритм, многие люди приходят к выводу, что рекурсия - проблема, и доходят до итерационного описания, чтобы достигнуть эффективности. Но проблема не в рекурсии, а в алгоритме. По сравнению с вычислением N-ro числа Фибоначчи методом "назад к нулю", гораздо эффективнее способ вычисления от нулевого до N-ro числа Фибоначчи, использующий две переменные для сохранения двух последних вычислений числа Фибоначчи. Пример рекурсивного описание для чисел Фибоначчи: $ (DEFUNFIB(NF1 F2) ((ZEROPN) Fl) (FIB(-N 1)(+F1 F2)F1)) FIB - функция трех параметров. Первый параметр - N, второй должен быть 1 и третий - 0. Если вы хотите, чтобы у функции Фибоначчи был один параметр, то определите "интерфейсную" функцию, которая просто вызывает FIB с соответствующими вторым и третьим параметрами. Возведение в целую степень является важной математической операцией. Чтобы возвести N в М-ую степень все, что вы должны сделать - умножить N на себя М раз. Итерационное описание функции POWER: $ (DEFUN POWER (N M % Local: % RSLT) (SETQ RSLT 1) (LOOP ((ZEROP M) RSLT) 33 (SETQ RSLT (* N RSLT)) (SETQM(SUB1 M)))) Пример: $ (POWER 2 4) $ 16 Вызов примитивной функции SUB1 в вышеупомянутом описании эквивалентен, но более эффективен, чем был бы вызов (-N 1). ADD1 - также примитивно определенная функция MuLISP. Рассмотрим следующие факты: 1). Если М - четное число, тогда N в степени М равняется N квадрат в степени М/2. 2). Если М - нечетное число, тогда N в степени М - N*N в степени (М-1). Для выполнения этого алгоритма нужна примитивная функция распознавания EVENP. Она возвращает Т тогда и только тогда, когда параметр -целое четное число; иначе она возвращает NIL. Рекурсивное описание POWER: $ (DEFUN POWER (N M) ((ZEROP M) 1) ((EVENP M) (POWER (*N N)(/M 2))) (+ N (POWER N (SUB1 M)))) Примитивная функция TRUNCATE полезна для преобразования дробей и частных в целые числа. Если <п> - число, (TRUNCATE n) округляет <п> до самого близкого целого числа в сторону нуля: Пример: $ (TRUNCATE 7/3) $2 При вызове с двумя параметрами, TRUNCATE возвращает их частное до целого значения. Обратите внимание, что (TRUNCATE n m) эквивалентно (TRUNCATE (/n m)): Пример: $ (TRUNCATE 7 3) $2 TRUNCATE возвращает округленное до целого числа частное двух параметров. Примитивная функция REM возвращает соответствующий остаток от деления двух целых чисел: N = Q * m + г 34 Часто нужно и частное, и остаток с помощью только одной операции. Примитивная функция DIVIDE возвращает последовательность частного и остатка от деления двух параметров. Примеры: $ (TRUNCATE 7 3) $2 $ (REM 7 3) $ 0.3333 $ (DIVIDE 7 3) $ (2.3333 0.3333) Наибольший общий делитель (НОД) двух чисел - самое большое неотрицательное целое число на которое делятся оба числа. Алгоритм Евклида для НОД целых чисел N и М может быть перефразирован так: 1. Если N=0, тогда НОД (N, М)=М; 2. Иначе, НОД (N, M)=(M mod N, N). Используем функцию REM для получения М mod N. Пример рекурсивного описание НОД с использованием определения Евклида: $ (DEFUN GCD (N М) ((ZEROP N) М) (GCD (REM M N) N)) Пример: $(GCD2166) $3 Фактически, функции НОД и НОМ (наименьший общий множитель) определяются в MuLISP примитивно. Конечно примитивные версии быстрее и могут работать с произвольным числом параметров. В заключении мы должны упомянуть примитивные числовые функции сравнения: =, /=, <, >, <= и >=. Примеры: $ (=34 34.0); Равно $Т $ (/=3/4 0.75) ; Неравно $NIL $ (<67 45) ; Меньше $NIL 35 $ (>=10 17 17) ; Больше-равно $Т Как показывает последний пример, числовые функции сравнения можно вызывать с несколькими параметрами. Если вызывать с нечисловыми параметрами, то произойдет прерывание и появится сообщение об ошибке -"Нечисловой параметр". Чтобы определить, находится ли номер в пределах данного интервала, функцию < можно вызывать с тремя параметрами. Пример: определим является ли "g" символом нижнего регистра: $ (<96 (ASCII 'g) 123) $Т Функция ASCII возвращает код ASCII, если дано символьное имя. Оно возвращает эквивалентный символ ASCII, если дано число между 0 и 256. Функция распознавания LOWERCASE, которая определяет является ли символ символом нижнего регистра: $ (DEFUN LOWERCASE (CHAR) (<96 (ASCII CHAR) 123)) Пример: $ (LOWERCASE 'g) $T Опишем функцию сортировки списка чисел в порядке увеличения. В данном случае нам не обязательно заботиться об эффективности, поэтому используем простую "сортировка-вставки", алгоритм которой подходит для коротких списков. Сначала нам нужна функция, которая вставляет число в соответствующее место в сортируемом списке номеров. Требуется написать функцию INSERT-NUM, которая вставляет NUM в LST. Пример рекурсивного определения функции INSERT-NUM: $ (DEFUN INSERT-NUM (NUM LST) ((NULL LST) (LIST NUM)) ((< NUM (CAR LST)) (CONS NUM LST)) (CONS (CAR LST)(INSERT-NUM NUM (CDR LST)))) Пример использования: $ (INSERT-NUM 12 '(5 9 11 14 19 21)) $ ( 1 2 5 9 11 14 1921) 36 Используем функцию INSERT-NUM чтобы написать функцию NUMBER-SORT, которая сортирует список чисел. Пример рекурсивного определения функции NUMBERT-SORT: $ (DEFUN INSERT-NUM (NUM LST) ((NULL LST) (LIST NUM) ((< NUM (CAR LST)) (CONS NUM LST)) (CONS (CAR LST)(INSERT-NUM NUM (CDR LST)))) Встроенная функция SORT использует эффективную сортировку с объединением списка. Если <test> - функция сравнения, (SORT list test) сортирует и возвращает <list> основанный на <test>. Пример: $ (СОРТИРОВКА "(34 23 -14 27 56 22 83) "<) $(-14 22 23 27 34 56 83) На этом заканчивается наше обсуждение числовой техники программирования MuLISP.