Точно Не проект 2 / Не books / Источник_1
.pdfГЛАВА 5
ЯЗЫК ЛИСП
Вэтой и следующей главе рассматриваются две важные парадигмы программирования, используемые при построении СИИ, – функциональное
илогическое программирование. Функциональное программирование реализуется средствами языка Лисп, а логическое – языка Пролог. Важная характеристика этих языков – ориентация не на числовую обработку, а на обработку символов. Поэтому структуры данных, поддерживаемые этими языками, не ограничиваются массивами. Списки – основная структура данных, реализованная как в языке Лисп, так и в языке Пролог.
Вязыке Лисп cписки используются и для представления обрабатываемых данных, и для представления самих программ. Пролог базируется на языке исчисления предикатов и также имеет удобные средства для обработки списков. Поддержка этими языками более мощных абстракций данных значительно повышает эффективность труда программиста. Согласно данным, приведенным в [7], в десять и более раз.
По этим причинам значительная часть прототипов программ с элементами ИИ реализуется на языках Лисп или Пролог. После выяснения функциональной работоспособности таких программ, они могут быть переписаны с помощью других языков для повышения эффективности реализации.
5.1.Общая характеристика языка
Язык Лисп был разработан в конце 50-х годов Дж. Маккарти. Название языка Лисп происходит от английских слов LISt Processing – обработка списков. Существовавшие в то время языки программирования были плохо приспособлены для решения задач ИИ, так как предназначались для обработки числовой информации. Цель, поставленная Маккарти, состояла в том, чтобы создать язык, ориентированный, прежде всего, на символьную обработку, а не на числовые вычисления, а также реализовать модель вычислений, основанную на теории рекурсивных функций А. Черча [26].
Язык Лисп |
211 |
Первая версия языка Лисп появилась в 1960 г. Основная особенность языка состояла в том, что как данные, так и программы записывались в виде списков. При этом список в языке Лисп представлялся в форме множества из одного или более элементов, заключенных в круглые скобки. Это обеспечивало с одной стороны очень простой синтаксис языка, а с другой – достаточно высокий уровень абстракции данных, необходимый для решения задач ИИ.
Представление данных и программ в одной и той же форме позволяет, при необходимости, интерпретировать данные как программы. Это дает возможность одним программам на языке Лисп генерировать другие программы, что характеризует Лисп как весьма гибкий язык.
Говоря о Лиспе, нельзя не отметить другую его характеристику. Лисп
– это язык функционального программирования. Функции являются основным инструментом для манипуляций со списками. В состав Лиспа входит большое число разнообразных функций обработки списков. Это позволяет относительно легко писать и отлаживать сложные программы.
Еще одна важная характеристика языка Лисп – это рекурсивный стиль написания многих функций. Рекурсивные определения имеют мощный математический фундамент, что позволяет выполнять аналитические исследования корректности функций, записывать их в ясной и лаконичной форме.
Сразу после появления первой версии языка начались работы по его совершенствованию. В 1961 г. была разработана новая версия языка под названием Lisp 1.5, которая претендовала на роль квазистандарта в период
с1961 по 1965 г. К началу 70-х годов появляются два новых диалекта языка Лисп – MacLisp и InterLisp. В диалекте MacLisp были введены функции
спеременным числом аргументов и возможности работы с макросами. В диалекте InterLisp появились итерационные циклы, которые позже вошли во многие другие диалекты языка. Необходимо отметить, что к этому времени уже существовало значительное число различных диалектов языка Лисп, и встал вопрос о его стандартизации. Первые усилия по стандартизации языка были предприняты в 1965 г., когда на основе Lisp 1.5 был разработан язык Standard Lisp. Это был первый диалект языка Лисп, реализованный на различных программно-аппаратных платформах. Существенное влияние на развитие языка Лисп оказало появление во второй половине 70- х годов диалекта Scheme и технологии объективно-ориентированного программирования в языках Smalltalk и KRL. В диалекте Scheme было расширено понятия области действия переменных и введено понятие лексического замыкания. К началу 80-х годов вновь остро стал вопрос о стандартизации языка. В 1981 г. агентство DARPA при Министерстве обороны США инициировало работы по разработке нового стандарта языка. В ре-
212 |
Глава 5 |
||
|
|
|
|
зультате этих работ в 1984 г. была разработана версия языка под названием Коммон Лисп (Common Lisp). Язык Коммон Лисп вобрал в себя достоинства многих диалектов Лиспа, существовавших к тому времени – Machine Lisp, Mac Lisp, NIL, S-1 Lisp, SpiceLisp, Scheme. Хотя различные диа-
лекты языка Лисп и оказали существенное влияние на Коммон Лисп, его не следует рассматривать как конгломерат отдельных элементов, заимствованных из них. Коммон Лисп – это высоко организованная, мощная среда программирования, содержащая весь арсенал средств, необходимый для разработки больших программных систем. Современные версии языка Коммон Лисп являются объектно-ориентированными, включают в себя среду визуального программирования и, по удобству работы, не уступают таким популярным оболочкам как DELPHI или CBuilder. Поэтому ниже будет рассматриваться именно этот диалект языка. С помощью Лиспа реализованы многие программные системы. Одна из самых известных систем – это AutoCAD фирмы AutoDesk.
5.2. Основные понятия языка
Лисп предназначен для обработки символьной информации. Поэтому основным типом данных, используемым в языке, является символьное выражение или s-выражение. Для того чтобы определить это и другие базовые понятия языка, воспользуемся расширенными металингвистическими формулами Бэкуса-Науэра (БНФ). При этом будем использовать обозначения, приведенные в таблице 5.1.
Алфавит языка Лисп состоит из цифр, букв и специальных знаков:
цифры::= 0| 1| 2| 3| 4| 5| 6| 7| 8| 9
буквы::= А| В| С … Х| Y| Z| a| b| c … x| y| z спецзнаки::= +| -| *| %| &| _| !| @| <| >| =| ?| /| {| }| ^| ~
Таблица 5.1 – Значения символов БНФ
Метасимвол |
Значение |
: : = |
“есть по определению” |
| |
или (альтернатива) |
{Х}* |
ноль или более вхождений Х, разделяемых пробелом |
{Х}+ |
одно или более вхождений Х, разделяемых пробелом |
… |
дальнейшее следование элементов |
[Х] |
ноль или одно вхождение Х |
Метаимя |
нетерминальный символ с именем Метаимя |
|
|
Язык Лисп |
213 |
Важное понятие языка Лисп – символы. Символы обозначают объекты, которыми манипулирует программа, и в этом смысле соответствуют именам переменных алгоритмических языков программирования. Однако символы в языке Лисп имеют и другие атрибуты, о которых будет сказано позже. Поэтому не следует их отождествлять с именами переменных алгоритмических языков программирования. Символы образуются из знаков алфавита по правилу:
символ::= буква | символ буква | символ спецзнак | символ цифра | цифра буква | цифра спецзнак | цифра символ | спецзнак символ
Примеры символов: А1, %1А, col13. В Лиспе не различаются прописные и строчные буквы. Поэтому символы, составленные из строчных букв, совпадают с соответствующими символами, составленными из прописных букв. Символы могут иметь значения. Для приписывания символам значений используются специальные формы. Изначально символы значений не имеют.
Наряду с символами, в Лиспе используются и числа. Лисп поддерживает работу с целыми, рациональными и вещественными числами:
число::=целое число | рациональное число | вещественное число
Числа записывают с помощью цифр и букв и, в отличие от символов, обозначают самих себя. Примеры чисел: 537, 2/3, 2.0Е-3.
Символы и числа представляют простейшие объекты языка Лисп, поэтому их называют атомами. Атом определяется с помощью следующей формулы:
атом::=символ | число | ()
Пара скобок ( ) в языке Лисп имеет двойное значение. С одной стороны, она обозначает пустой список, а с другой – логическое значение ложь. Иными словами, в Лиспе ложь представляется пустым списком. Все остальные значения интерпретируется как истина. Для обозначения логического значения истина (true) в Лиспе используется символ Т. Ложь или пустой список также обозначаются символом NIL. Символы Т и NIL имеют фиксированное значение, и их нельзя использовать в качестве имен других лисповских объектов.
Из атомов можно построить основную структуру данных языка Лисп
– s-выражение, которое определяется с помощью следующей рекурсивной формулы:
s-выражение ::= атом | (s-выражение . s-выражение )
214 |
Глава 5 |
||
|
|
|
|
Приведем примеры s-выражений, составленных в соответствии с введенным определением:
gruppa
(gruppa1 . gruppa2) ((gruppa1 . gruppa2) . gruppa3)
Обратите внимание на то, что точка отделяется от знаков, стоящих слева и справа, пробелом. Конструкция (s-выражение . s-выражение) представляет собой точечную пару. Элементами точечной пары могут быть как атомы, так и точечные пары. Точечную пару можно определить с помощью следующей БНФ:
точечная пара ::= ( атом . атом )| ( атом . точечная пара )|
( точечная пара . атом )| ( точечная пара . точечная пара )
Удобно точечные пары изображать в виде диаграмм. На рисунке 5.1 показана диаграмма для точечной пары (А . В). Такая диаграмма дает наглядное представление о форме хранения точечной пары в памяти ЭВМ и называется лисповской ячейкой.
Рисунок 5.1 – Лисповская ячейка
Эта ячейка состоит из двух полей. В первом поле хранится указатель на первый элемент точечной пары, а во втором поле – указатель на второй элемент.
Если элементами точечной пары являются тоже точечные пары, то в ячейке хранятся два указателя, ссылающиеся на другие ячейки. На рисунке 5.2 изображена диаграмма для точечной пары ((А . В) . (С . D))
Сравнивая определение s-выражения и точечной пары, приходим к выводу, что
s-выражение ::= атом | точечная пара
В Лиспе выделяют специальный тип s-выражений, который называется списком. Список определяется следующим образом:
cписок ::= NIL | (( s-выражение . список ))
Язык Лисп |
215 |
Например, s-выражения (A .(B .(C .(D . NIL)))) и ((A . B) . ((C . D) . NIL)) – списки. Элементы первого списка – это атомы A, B, C, D, элементы второго списка – это точечные пары (A . B) и (C . D) (рисунок 5.3).
Рисунок 5.2 – Диаграмма точечной пары ((A . B ) . (C . D))
В Лиспе принято также изображать список в виде последовательности элементов, отделяемых пробелом и заключенных в круглые скобки: (А В С D), ((А . В) . (С . D)). В этом случае список представляет собой упорядоченную последовательность, элементами которой являются как атомы, так и списки. Списки, записанные в такой форме, весьма удобны для представления различных моделей знаний. Вспомните, например, фреймовые модели. Список, заданный в виде последовательности элементов, всегда можно представить в точечной записи. Например, следующие записи эквиваленты:
(А В С D) ↔ (A . (B . (C . (D . NIL))))
а)
б)
Рисунок 5.3 – Диаграммы списков (А. (В . (С . (D . NIL)))) (a) и
((А . В) . ((С . D) . NIL)) (б)
216 |
Глава 5 |
||
|
|
|
|
Однако не каждое s-выражение, записанное в точечной форме, является списком. Например, выражение (А . (В . (C . D))) не является списком, так как не соответствует определению понятия список.
Для перехода от точечной записи выражения к записи его в форме последовательности элементов используют следующее правило. Если в s- выражении встречается точка, стоящая перед открывающейся круглой скобкой, то эту точку можно заменить пробелом, удалив упомянутую открывающуюся и парную закрывающуюся скобку. Так, s-выражение (A . (B . (C . D))) приводится к виду (А В С . D). Отсюда следует, что оно не является списком. Вместе с тем запись (А . (В . (С . (D . NIL)))) легко преобразуется к виду (А В С D) , если учесть, что атом NIL эквивалентен паре скобок (). Существует и обратное правило, позволяющее восстановить точечную запись [48].
Отметим, что некоторые записи, составленные из символов, точек и круглых скобок, не будут являться s-выражениями. Так, запись (А . В С) не может быть получена с помощью введенных выше определений, а потому не является s-выражением.
5.3.Интерпретация Лисп-программ
ВЛиспе не только данные представляются в виде s-выражений, но и программы. Выполнение программы состоит в вычислении s-выражений. Однако не любое s-выражение может быть вычислено. Вычисляемое s- выражение характеризуется определенной семантикой, которая анализируется интерпретатором.
Если результаты анализа не противоречат семантике языка, то интерпретатор вычисляет значение s-выражения. S-выражение, значение которого может быть вычислено Лисп–интерпретатором, называется формой.
На рисунке 5.4 приведена обобщенная схема интерпретации s- выражений, работа которой состоит из трех шагов. На первом шаге происходит считывание s-выражения с помощью встроенной функции READ.
Рисунок 5.4 – Схема интерпретации s-выражений
Язык Лисп |
217 |
По умолчанию исходные s-выражения вводятся с клавиатуры. На втором шаге выполняется интерпретация s-выражения с помощью функции ЕVAL, которая является вызовом интерпретатора. На третьем шаге функция PRINT осуществляет вывод значения s-выражения. По умолчанию вычисленные значения выводятся на дисплей. Затем цикл READ-EVAL- PRINT повторяется для другого s-выражения и т.д.
Программы на языке Лисп состоят из форм и функций. Лисповская функция может рассматриваться как коллекция форм, которые вычисляются при вызове функции. В свою очередь, вызов функции выполняется обычно по ее имени из формы, записанной в виде списка:
(fn a1 a2 … an),
где fn – имя вызываемой функции; а1, а2 … аn – аргументы функции, которые задаются вычисляемыми s- выражениями.
Например, если требуется вычислить сумму чисел 5 и 3, то с клавиатуры вводится следующее s-выражение (+ 5 3). Результат интерпретации показан на рисунке 5.5. Здесь знак “+” соответствует имени функции, выполняющей сложение.
> : ( + 5 3) |
; вводимое S-выражение |
8 |
; результат |
>: |
; приглашение ввода |
Рисунок 5.5 – Пример интерпретации
В общем случае формы могут быть заданы константами, перемен-
ными, списками. Константы соответствуют самоопределяемым формам,
которые представляют самих себя и имеют фиксированные значения (числа, символы T и NIL, строки и др.). Переменные в Лиспе обозначаются символами. При вычислении такой формы возвращается значение символа. Форма, заданная в виде списочной структуры, может представлять: вызов функции, рассмотренный выше, вызовы специальных форм, макровызовы. Специальные формы позволяют выполнять действия, недостижимые с помощью обычных функций, например, присваивать значения переменным, осуществлять условные вычисления. К таким формам относят: SETQ, QUOTE, IF и др. Макровызовы внешне соответствуют вызову функции, но отличаются по способу вычисления. Они вычисляются в два этапа: сначала из аргументов макроса строится форма, а затем она вычисляется. Подробнее макросы будут рассмотрены позже.
218 |
Глава 5 |
||
|
|
|
|
При вычислении s-выражений, представленных в форме констант, переменных или списков, интерпретатор EVAL реализует следующие упрощенные правила:
а) если s-выражение – это константа (самоопределяемая форма), например, число или атомы T и NIL, то EVAL возвращает такое s-выражение без изменений;
б) если s-выражение – это переменная, представленная символом, то функция EVAL возвращает последнее значение, которое было связано с этим символом; если символ не имеет значения, что выдается сообщение об ошибке;
в) если s-выражение – это список, и первый элемент списка – символ, то EVAL интерпретирует его либо как имя функции, либо как имя специальной формы, либо как имя макроса. Указанные имена должны быть известны системе. Если первый элемент списка – имя функции, то EVAL интерпретирует оставшиеся элементы списка как ее аргументы, которые подлежат вычислению с помощью EVAL.
Важным достоинством режима интерпретации является удобство отладки программ. Если случаются ошибки, то их можно тут же исправить, введя правильное s-выражение. Недостатком режима интерпретации является низкая скорость выполнения программы. Режим компиляции программ позволяет существенно улучшить этот показатель [48].
5.4.Арифметические функции
ВЛиспе имеется большое количество встроенных арифметических функций. Аргументы этих функций должны иметь числовые значения. Числа в Лиспе подразделяются на целые, вещественные и рациональные. Рациональные числа представляются в виде дроби, например, 2/3. Кроме этого, в Лиспе предусмотрена обработка комплексных чисел. Комплексное число 5+3i на языке Лисп записывается в форме #C(5 3).
Ниже приведены примеры вызовов функций, выполняющих основные арифметические операции:
(+ 2 3) |
5 |
; сложение 2-х аргументов |
||
(+ 3 1 2 3) |
9 |
; сложение 2-х и более аргументов |
||
(+ |
9) |
9 |
; унарный + |
|
(- 13 1 2 3) |
7 |
;вычитание 2-х и более аргументов |
||
(- |
- |
9) |
9 |
; |
(* |
2 |
1.5) |
3,0 |
; умножение 2-х аргументов |
(* 2 |
2 3) |
12 |
;умножение 2-х и более аргументов |
|
(/ 7 2 2) |
7/4 |
;деление целочисленных аргументов |
Язык Лисп |
|
|
|
219 |
(/ 7 2 2.0) |
|
1.75 |
;деление с преобразованием |
|
|
|
|
0.2 |
;к вещественному значению |
(/ 5.0) |
|
|
; обратное значение |
|
(/ 2) |
|
|
1/2 |
; |
(min 2 |
3 |
1) |
1 |
; минимум |
(max 2 |
3 |
1) |
3 |
; максимум |
(abs - 5) |
|
5 |
; абсолютное значение |
|
(rem 17 |
4) |
1 |
; остаток от деления. |
Если в арифметических операциях участвуют вещественные аргументы, то результаты представляются в вещественной форме.
Для сравнения скалярных чисел в Лиспе используются функции, имена которых задаются знаками отношений ( =, | = (не равно), <, >, <=, >= ). Эти функции могут иметь произвольное количество аргументов. В этом случае проверяется выполнение соответствующего отношения для всех аргументов. Если отношение выполняется, то функция возвращает в качестве результата значение Т, иначе – NIL. Например,
(> 5 4 3 2 1) Т
В Лиспе имеется большой набор иррациональных и трансцендентных функций: EXP, EXPT, LOG, SQRT, SIN, COS, TAN, ASIN, ACOS, ATAN
и др. Аргументы тригонометрических функций задаются в радианах. Ниже приведены примеры вызовов некоторых из указанных функций:
(expt 2 10) |
1024 |
(log 10 2) |
3.321928 |
(sqrt -4) |
#C(0.0 2.0) |
Функции TRUNCATE, ROUND, FLOAT выполняют преобразование чисел из одного формата в другой. Функция TRUNCATE возвращает два значения – целую и дробную часть числа:
(truncate - 1.7) - 1 - 0.7
Функция ROUND выполняет округление числа и также возвращает в качестве результата два значения – ближайшее целое и разность между исходным значением и найденным ближайшим целым:
(round 5.7) 6 - 0.3
Функция FLOAT преобразует целое число в число с плавающей точ-
кой.