[Изображение логотипа компании]

Домой Вверх Содержание

Lisp - первые шаги
Вверх Lisp - первые шаги Lisp -атомы и списки Lisp - переменные Lisp - вызов функций Программные блоки Структуры данных

 

 

 

В.Водолазкий

Первые шаги в GNU Common Lisp


В статье рассматриваются начальные шаги по освоению языка программирования Lisp. Описывается процедура установки среды в системе Linux и приводятся примеры нескольких простых программ. Описание основано на версии GNU Common Lisp.


Наверняка читатели слышали о языке Лисп. И я более чем уверен, что подавляющее большинство никогда не видело Лисп в работе, ну, возможно, за исключением простых программ на Автокаде или десятистрочных "поделок" в рамках семинаров по курсу "алгоритмические языки и программирование". Причин такому положению дел достаточно много, но как ни удивительно, в основе лежит несколько мифов...

Мифы о языке Лисп

Вот несколько наиболее распространенных "страшных историй" с которых я хотел бы начать.
Лисп безнадежно устарел. Самый старый миф. Действительно, Лисп появился еще в конце 50-х годов прошлого века, и уже тогда заметно отличался от своих собратьев. Но собственно говоря, уже тогда начало формироваться два класса языков - процедурный ( тот же Фортран, Алгол и т.д.) и функциональный, примерами которых являются Лисп. РЕФАЛ, Форт и еще несколько других. За эти годы многие из языков, которые громко объявлялись как "универсальные" (вспомним Кобол и PL/1) постепенно канули в Лету, а Лисп, как хорошее марочное вино, постепенно набирал выдержку и становился все мощнее и интереснее. В современных версиях языка (о которых мы и будем говорить), вы найдете все те решения, которые были беззастенчиво позаимствованы в С++, Perl и других популярных языках. Но, увы, копии не смогли превзойти оригинал...
На Лиспе никто не пишет... Ага, это секретный язык г-на Водолазкого... Он владеет им в гордом одиночестве! Ну что вы право... Взгляните, например, на конференцию USENET comp.lang.lisp. Ежедневно в нее помещается порядка 350-500 статей, что возможно, меньше, чем в конференцию, посвященную Delphi, но вот уровень поднимаемых вопросов гораздо выше. Лисп является исключительно популярной системой при реализации встроенных языков расширения программных систем. Самые популярные примеры - EMACS и Autocad, но пользователям Linux стоит упомянуть также и GIMP.  Стоит также упомянуть, что программисты на Лиспе являются предметом постоянной охоты западных рекрутинговых агентств и с завидным постоянством выявленные таланты вывозятся с территории стран СНГ. Впрочем, если учесть, что министерство обороны США объявило Common Lisp официальным языком для построения военных систем искусственного интеллекта, это неудивительно.
Я не вижу спроса на Лисп-программирование. Спрос бывает разный. Программисты на Лиспе действительно не нужны при разработке бухгалтерских программ. У Лиспа совершенно другая ниша - это язык для решения действительно интересных, а потому  сложных задач. Кстати, задачи искусственного интеллекта являются только небольшой областью, в которой Лисп занимает ведущие позиции во всем мире. Программисты на Лиспе являются наиболее тщательно оберегаемыми от внешнего мира интеллектуальными ресурсами крупнейших компаний - именно они разрабатывают программные комплексы стратегического планирования и анализа, а поэтому реклама им не нужна - хорошие заработки и сдувание пылинок гарантированы!
В Лиспе нет графического интерфейса... Просто потрясающе! Самый первый графический интерфейс, с которого впоследствии были слизаны и Microsoft Windows и MacOS были реализованы именно на Лисп-машине! Никаких проблем в том, чтобы реализовать графический интерфейс и на обычной платформе PC нет - и в этом вы убедитесь прямо сегодня!
Лисп работает очень медленно. Честно говоря, я тоже так думал. Но еще лет десять назад, под влиянием нескольких публикаций о реализации Лиспа на Бейсике, я написал небольшой (но полностью функциональный и расширяемый) интерпретатор Лиспа на языке Паскаль для ДВК-3. И что вы думаете? Мой доморощенный Лисп работал на вычислительных задачах быстрее, чем родной Бейсик фирмы DEC! А вывод прост - эффективность языка во многом определяется как его реализацией, так и качеством прикладных программ. Ну да ладно хвастаться былыми достижениями, современные версии Лиспа (например, GNU Common Lisp) построены куда изящнее, чем моя поделка и на современных платформах работаю весьма и весьма прилично. А если добавить возможность распараллеливания работы Лисп-программ на нескольких машинах в сети (при желании читателей этому вопросу будет посвящена отдельная статья), то становится ясно, что никаких принципиальных ограничений для производительности Лисп-систем сегодня нет.
Для Лиспа нет компилятора. Вообще то, это так, поскольку для серьезных Лисп-программ полная компиляция противопоказана - ведь в этом языке единство кода и данных поддерживается на все 100% и зачастую программа синтезируется непосредственно во время выполнения. Но что касается использования компилятора для генерирования промежуточного кода, ускоряющего последующую загрузку и выполнение программ, а также защиту исходного кода программ, то такие решения есть, и входят в состав стандартных дистрибутивов (вернее сказать, встроены в саму среду Лиспа).
В Лиспе слишком много скобок... Ах, мне бы ваши проблемы! Тосно так же можно утверждать, что в Паскале слишком часто используется двоеточие, а в Си - точка с запятой. Да, плохо написанная программа просто пестрит скобками, но ведь это именно плохо написанная программа, а не Лисп как таковой...

Лисп шаг за шагом

Ну что же, если вы хотя бы частично согласились с моими доводами, давайте попробуем пощупать Лисп собственными руками. В принципе, практически в любой дистрибутив Linux входит та или иная версия Лиспа, но мы будем вести речь об "официальной версии" проекта GNU, которая по вполне понятным причинам называется GNU Common Lisp (GCL). На момент написания этих строк последней версией на сервере ftp.gnu.org являлась версия 2.4.1. О ней пойдет речь ниже. В состав Slackware 8.0 входит установленная система GCL 2.4.0. Все описываемые примеры вполне работоспособны и в ней, но все равно я рекомендую вам извлечь дистрибутив в котором вы найдете массу полезной информации.

Установка GCL

Первое, с чего следует начать, это установить Лисп на вашей машине. К слову сказать, если на вашем компьютере установлен дистрибутив Slackware 4.0, то при работе на машинах класса выше Pentium-I GCL работать откажется - скажутся ошибки в устаревших версиях системных библиотек. Так что позаботьтесь об обновлении программного обеспечения. Итак, ваша задача извлечь c с сервера GNU или одного из его зеркал дистрибутив Common Lisp, который находится в файле gcl-2.4.1.tgz , а затем распаковать его (я обычно использую для этой цели каталог /usr/src) и собрать:

cd /usr/src
tar xvfz gcl-2.4.1.tgz
cd gcl-2.4.1
./configure
make 
make install

Все! Система установлена и готова к работе. Никаких перезагрузок не потребуется. Теперь давайте проверим ее работоспособность.

Первый вызов Лиспа


 Для этого нам нужно вызвать интерпретатор Лиспа с помощью команды gcl . Ну а что делать дальше, показано на рис.1

Первая проба пера в Лиспе

Рис.1. Первая проба пера в Лиспе

Итак, что мы видим... Во-первых, разработчики забыли сменить номер версии, и хотя мы устанавливали GCL 2.4.1 система не признает новый номер версии и по-прежнему выводить старый. Но это опытного программиста пугать не должно. Далее вы можете увидеть как с помощью встроенных функций Лиспа можно решать вычислительные задачи. Несмотря на кажущуюся непривычность записи все достаточно просто. Лет сто назад Владимир Ильич Ленин по какому-то случаю заметил, что весь мир можно описать с помощью дифференциальных функций. Лисп некоторым образом поддерживает этот подход - в нем все также представляется с помощью функций. Любое выражение в Лиспе представляет собой вызов функции, оформленной достаточно стандартным образом,

(имя_функции  аргумент_1  аргумент_2 ...)

В данном случае на рис.1 представлено три стандартные (то есть встроенные) функции -  +, - и bye. Последняя функция, хотя и вызывается вообще без аргументов очень важна, поскольку никакими другими способами, за исключением снятия задачи с помощью команды kill вы покинуть Лисп не сможете.  Стандартных функций в Лиспе за последние полсотни лет накопилось достаточно много, но знать их все вам не потребуется - Лисп относится к тем языкам, где зачастую проще заново изобрести велосипед, предназначенный для одноразовой поездки, чем изучать неизвестную вам до сих пор стандартную функцию.

Создание собственных функций


Теперь давайте попробуем продвинуться чуть дальше. Итак, мы уже знаем, как вызывается функция с аргументами-константами, но ведь нам может потребоваться вычислить значение этих аргументов? Давайте в качестве примера рассчитаем длину гипотенузы прямоугольного треугольника со сторонами 3 и 4. Для этого вам достаточно ввести в gcl команду:

(sqrt (+ (*3 3) (* 4 4)))

В результате на выходе вы получите число 5.0. Обратите внимание, что Лисп самостоятельно осуществляет приведение типов переменных и вам обычно не нужно преобразовывать тип аргумента или результата к какому-либо конкретному виду. Эта черта Лиспа беззастенчиво воровалась огромным количеством разработчиков и не раз выдавалась за "уникальное достижение", хотя впервые появилась еще 50 лет назад.

Ну да ладно, пора двигаться дальше. Давайте попробуем теперь создать собственную функцию, которая позволит нам рассчитывать гипотенузу для любого прямоугольного треугольника. Для этого нам потребуется так называемая специальная форма   - конструкция Лиспа, обеспечивающая генерацию исходного кода программы. Впрочем, для программиста эта форма ничем не отличается от функции. Имя этоф формы defun и общий ее вид следующий,

(defun   ( <список_аргументов>)
      <тело-функции>
 )
Понятно, что список аргументов может быть пустым, как например, у функции bye. Что касается тела функции, то это последовательность вызова функций Лиспа, которые реализуют логику работы создаваемой нами функции. Любая функция возвращает результат - это значение, получаемое в результате вычисления последней функции в теле. Фуу-ф, понятие "функция" настолько часто встречается в Лиспе, что избежать тавтологии кажется совершенно невозможным...


Но ближе к делу, чтобы продемонстрировать еще пару моментов, мы поступим следующим образом. Открывайте любой текстовый редактор и вводите определения, представленные на рис.2.



Рис.2. Программа расчета сторон прямоугольного треугольника

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

Второе, на что необходимо обратить внимание - это определение функций. Я глубоко убежден, что писать тексты программ по-русски все же удобнее, чем по-английски, особенно, если оценки за "аглицкий" в школе выше тройки поднимались редко. И мое субъективное  мнение разделяют  французы (книги по Лиспу на французском используют примеры, написанные на французском) и даже финны, которых не больше, чем жителей в Московской области. А то, что мы используем кириллицу, только играет нам на руку - вы сразу видите, работаете ли вы со встроенной функцией, или со своей собственной.

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

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

И наконец, завершают программу несколько вызовов функций для вывода результатов расчетов. В данном случае нам достаточно трех из них.
Функция prin1 выводит на печать результат оценки значения своего первого аргумента, а функция princ интерпретирует свой аргумент как символьную строку. И наконец, terpri предназначена для перехода на новую строку. Конечно, в Лиспе имеются и более мощные, унифицированные средства форматирования входных и выходных потоков, но я пока не буду забивать вам голову этими материями...

Итак, с программой вроде бы разобрались. Предположим, вы поместили ее в файл gipotenuza.lsp. Теперь нам осталось запустить ее на выполнение. Сделать это можно двумя способами. Во-первых, вы можете вызвать gcl из командной строки, передав ему в качестве аргумента входной файл:

gcl -f gipotenuza.lsp

Файл будет загружен и автоматически запущен на выполнение. А поскольку последним вызовом функции в этом файле является команда завершения работы Common Lisp, на консоль будет выведена строка,

Треугольник имеет стороны: 12 5 13.0

Второй способ предполагает загрузку программы из самого Common Lisp. Для этого вы используете вызов функции load, которой в качестве аргумента передается имя файла.



Рис.3. Вызов программ на Лиспе из командной строки и из среды.

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

Работа с графическим интерфейсом

Ну что же, теперь пришло время познакомить читателя с использованием графического интерфейса. Одна из особенностей GCL заключается в том, что эта система позволяет достаточно просто подключать внешние библиотеки, входящие в состав Linux. Одной из таких библиотек является Tk - это бибилотека для быстрого проектирования графического интерфейса пользователя. Во время компиляции вашей Лисп-системы эта библиотека была подключена к интерпретатору Лиспа и нам остается ее только активизировать.

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

Ключевая конструкция для подключения графического интерфейса выглядит очень просто - это один единственный вызов функции

(si::tkconnect)

который приводит к открытию пустого окошка в среде X-Window (понятно, что запускать программу, ориетнированную на работу в "Иксах" вы должны при загруженном Х-сервере). Теперь давайте подойдем к проблеме радикально  - создадим аналог "Hello, World", ориентированный на работу в Tk-интерфейсе и поместим эту программу в файл msg.lsp.
Вот ее текст:
;;;
;;; msg.lsp - Программа вывода текстового окошка с испльзованием
;;;           встроенного шлюза Lisp-Tk
;;;

(si::tkconnect)   ; подключаем библиотеку Tk

(in-package "TK") ; включаем ее каталог в наше пространство симоволов


;; Функция wm предназначена для настройки параметров оконного
;; менеджера. Данный вызов позволяет изменить заголовок окна
 
(wm :title '|.|' "Всем привет от GCL 2.4.1")

;; Теперь создаем два фрейма в окошке - верхний для текстовых
;; сообщений, а нижний - для кнопок

(frame '.main :relief "raised" :borderwidth 0)
(pack '.main :side "top")

(message '.main.txt :relief "raised" :borderwidth 1
                :width 600  
                :text  "
                       Наша первая программа на Common Lisp!
                       Предназначена для вывода в окошке списка файлов
                       из корневого каталога системы
" )
(frame '.buttons :relief "raised" :borderwidth 1)
(pack '.buttons :side "bottom")

;; Помещаем фреймы в основное окно с использованием менеджера геометрии,
;; заимствованного из Tk в Java

(pack '.main.txt :side "top" :fill "x")

;; Теперь нам необходимо создать кнопочку, помещаемую в нижний кадр и
;; связать с ней некоторую операцию

(button '.buttons.ls :text "О программе" :command `(about))
(button '.buttons.ok :text "Закончить" :command `(bye))

(pack '.buttons.ls '.buttons.ok :side "left" :expand 1)
(pack '.buttons :side "bottom" :expand "yes" :fill "both")

(focus '|.|)


;; about - Новое сообщение

(defun about()
  (destroy '.main.txt)
  (message '.main.txt :text "Мы легко можем заменить текущее сообщение
в окошке программы на новое. А чтобы завершить работу
достаточно нажать кнопку \"Закончить\"" :width 600) 
   (pack '.main.txt)
)
В целом построение программы на Lisp/Tk не слишком отличается от проектирования графического интерфейса на языке Java. В конце концов, и Tk, и Java родились в лабораториях фирмы Sun и используют один и тот же механизм описания геометрии экранных элементов. Поэтому необходимо только указать  на общую последовательность действий. Вначале мы создаем корневое окно, которое имеет зарезервированное имя .,
хотя и записывается в виде |.| - это просто одна из маленьких хитростей на которых я не буду пока фиксировать ваше внимание. Затем мы начинаем вводить сегменты экрана - фреймы, которые не слишком отличаются от фреймов, используемых в HTML-файлах. А уже затем, в каждый фрейм мы помещаем тот или иной экранный элемент. В нашем случае это текстовое сообщение (обратите внимание на сохранение всех элементов форматирования строк)
Во втором, нижнем фрейме мы помещаем две кнопочки. И снова менеджер геометрии самостоятельно размещает их в пределах своей ответственности. Нам необходимо только задать имя кнопки и определить команду, которая будет выполняться при нажатии на нее. А поскольку Лисп - это постоянный вызов функций, то вполне логично связать с кнопкой либо свою собственную, либо системную функцию.
Вот как выглядит результат работы нашей программы:

  

Как видно из приведенных рисунков, при корректно настроенной системной локали и установленных кириллических шрифтах вы вполне можете обойтись без принудительного ввода описаний шрифтов. Впрочем видно также, что менеджер геометрии самостоятельно определяет размер окон, что не всегда желательно... Но в этом ничего страшного нет, вскоре вы узнаете, как загнать своевольный Tk в прокрустово ложе требований программиста. А пока что попробуйте "пощупать" Лисп самостоятельно - в следующем номере мы продолжим знакомство с основными конструкциями и приемами работы, но собственный опыт еще никому не мешал.

 

 

 

 

 

 

Послать письмо voldemarus@narod.ru  
Авторские права © 2003 Картонная армия
Последнее изменение: апреля 05, 2003
Используются технологии uCoz