Регистрация | Войти
Lisp — программируемый язык программирования
Оглавление Следующая

1. Введение: почему Lisp?

Если вы считаете, что наибольшее удовольствие в программировании доставляют большие результаты, достигнутые с помощью кода, просто и прозрачно выражающего ваши желания, тогда программирование на Common Lisp будет самым приятным из того, что вы можете делать на компьютере. Используя Common Lisp, вы достигнете б́ольших результатов за меньшее время по сравнению с другими языками программирования.

Серьёзное заявление. Могу ли я доказать это? Да, но не на нескольких страницах введения. Вам придётся познакомиться с Lisp поближе и убедиться в этом самим — так что всё-таки придётся читать книгу до конца. А сейчас, позвольте мне начать с нескольких смешных эпизодов, из истории моего пути к языку Lisp. В следующей главе я объясню выгоды, которые вы получите от изучения Common Lisp.

Я – один из немногих Lisp-хакеров во втором поколении. Мой отец начал заниматься компьютерами с написания на ассемблере операционной системы для машины, которую он использовал для сбора данных при подготовке его докторской диссертации по физике. После работы с компьютерами в разных физических лабораториях, к 80-м отец полностью оставил физику и стал работать в большой фармацевтической компании.

У этой компании был проект по созданию программы, моделирующей производственные процессы на химических заводах (если вы увеличите эту емкость, как это повлияет на годовой результат?). Старая команда писала всё на языке FORTRAN, использовала половину бюджета и почти всё отведённое время, но не могла продемонстрировать никаких результатов. Это было в 80-х, пик бума искусственного интеллекта (ИИ), Lisp так и витал в воздухе. Так что мой папа — в то время еще не поклонник языка Lisp — пошёл в университет Карнеги-Меллона, чтобы пообщаться с людьми, работавшими над тем, что впоследствии стало Common Lisp, и узнать, сможет ли Lisp стать подходящим языком для его проекта.

Ребята из университета показали ему кое-что из своих разработок, и это его убедило. Отец, в свою очередь, убедил своих боссов позволить ему взять провальный проект и сделать его на Lisp. Год спустя, используя остатки бюджета, команда отца представила работающее приложение, обладающее возможностями, на реализацию которых старая команда уже и не надеялась. Мой папа объясняет, что причина успеха – в решении использовать Lisp.

Однако, это всего лишь первый эпизод. Может быть, мой отец ошибался в причине своего успеха. Или, может быть, Lisp был лучше других языков лишь для того времени. В настоящее время мы имеем кучу новых языков программирования, многие из которых переняли часть достоинств Lisp. Действительно ли я считаю, что использование языка Lisp может дать вам те же выгоды, что и моему отцу в 80-х? Читайте дальше.

Несмотря на все усилия моего отца, я не изучал Lisp в университете. После учёбы, которая не содержала много программирования на каком-либо языке, я был покорен Web и вернулся назад к компьютерам. Сначала я писал на Perl, изучив его достаточно, чтобы создать форум для сайта журнала Mother Jones, после этого я работал над большими (по тем временам) сайтами, такими, как, например, сайт компании Nike, запущенный к олимпийским играм 1996 года. После этого я перешёл на Java, будучи одним из первых разработчиков в WebLogic (теперь эта компания — часть BEA). После WebLogic я учавствовал в другом стартапе, где был ведущим программистом по построению транзакционной системы обмена сообщениями на Java. Со временем мои основные интересы в программировании позволили мне использовать как популярные языки, такие, как C, C++ и Python, так и менее известные, такие, как Smalltalk, Eiffel и Beta.

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

Например, в одном из отпусков, имея около недели на опыты с Lisp, я решил попробовать написать версию программы, написанной мною на Java в начале программистской карьеры. Эта программа применяла генетические алгоритмы для игры в Го. Даже с моими зачаточными знаниями Common Lisp написание всего-лишь основных функций было намного продуктивнее, чем если бы я решил переписать всё на Java заново. Для написания программы на Java потребовалось несколько лет работы с этим языком.

Похожий эксперимент привёл к созданию библиотеки, о которой я расскажу в главе 24. В начале моей карьеры в WebLogic я написал библиотеку на Java для разбора java-классов (файлов *.class). Она работала, но код был запутан, и его трудно было изменить или добавить новую функциональность. В течение нескольких лет я пытался переписать библиотеку, думая, что смогу использовать мои новые знания в Java и не увязнуть в куче дублирующегося кода, но так и не смог. Когда же я попробовал написать её на Common Lisp, это заняло всего 2 дня, и я получил не просто библиотеку для разбора java-классов, но библиотеку для разбора любых двоичных файлов. Вы увидите, как она работает, в главе 24, и воспользуетесь ею в главе 25 для разбора тэгов ID3 в MP3-файлах.

Почему Lisp?

Сложно объяснить на нескольких страницах введения, почему пользователи языка любят какой-то конкретный язык, ещё сложнее объяснить, почему вы должны тратить своё время на его изучение. Личный пример не слишком убеждает. Может быть, я люблю Lisp, потому что какая-то цепь в моём мозгу замкнулась. Это может быть даже генетическим отклонением, так как мой отец похоже тоже имел его. Так что прежде, чем вы погрузитесь в изучение языка Lisp, вполне естественным покажется желание узнать, что это вам даст, какую выгоду принесёт.

Для некоторых языков выгода очевидна. Например, если вы хотите писать низкоуровневые программы для Unix, то должны выучить C. Или если вы хотите писать кросс-платформенные приложения, то должны использовать Java. И большое число компаний до сих пор использует C++, так что если вы хотите получить работу в одной из них, то должны знать C++.

Тем не менее, для большинства языков выгоду не так просто выделить. Мы имеем дело с субъективными оценками того, насколько язык удобно использовать. Защитники Perl любят говорить, что он "делает простые вещи простыми, а сложные - возможными" и радуются факту, озвученному в девизе Perl - "Есть более чем один способ сделать это".1) С другой стороны, фанаты языка Python думают, что Python – прозрачный и простой язык, и код на Python проще понять, потому что, как гласит их лозунг, "Есть лишь один способ сделать это".

Так почему же Common Lisp? Здесь нет такой очевидной выгоды, как для C, Java или C++ (конечно, если вы не является счастливым обладателем Lisp-машины). Выгоды от использования Lisp заключены в переживаниях и впечатлениях от его использования. В остальной части книги я буду показывать отличительные черты языка, так что вы сможете по себе оценить, на что эти впечатления похожи. Сейчас я попытаюсь показать смысл философии Lisp.

В качестве девиза для Common Lisp лучше всего подходит похожее на дзенский коан описание "программируемый язык программирования". Хотя данный девиз выглядит несколько запутанно, он, тем не менее, выделяет суть преимущества, которое Lisp до сих пор имеет перед другими языками программирования. Больше, чем другие языки, Common Lisp следует философии: что хорошо для разработчика языка, то хорошо для его пользователей. Программируя на Common Lisp, вы, скорее всего, никогда не обнаружите нехватки каких-то возможностей в языке, которые упростили бы программирование, потому что, как будет показано далее, вы можете просто добавить эти возможности в язык.

Следовательно, программы на Common Lisp стараются предоставить наиболее прозрачное отображение между вашими идеями о том, как программа должна работать, и кодом, который вы пишете. Ваши идеи не замутняются нагромождением кода и бесконечно повторяющимися выражениями. Это делает ваш код более управляемым, потому что вам больше не приходится бродить по нему всякий раз, когда вы хотите внести какие-то изменения. Даже систематические изменения в программе могут быть достигнуты относительно малыми изменениями исходного кода. Это также означает, что вы будете писать код быстрее; вы будете писать меньше кода и не будете терять время на поиск пути для выражения своих идей в ограничениях, накладываемых языком программирования2).

Common Lisp — это также прекрасный язык для исследовательского программирования (прототипирования?), когда вам неизвестно достоверно, как ваша программа должна работать. Common Lisp предоставляет некоторые возможности, помогающие вам вести инкрементальную интерактивную разработку.

Интерактивный цикл read-eval-print, о котором я расскажу в следующей главе, позволяет вам непрерывно взаимодействовать с вашей программой во время её разработки. Пишете новую функцию. Тестируете её. Меняете её. Пробуете другие подходы к реализации. Вам не приходится останавливаться для длительной компиляции3).

Другими особенностями, которые поддерживают непрерывный, интерактивный стиль программирования, являются динамическая типизация Lisp и система обработки условий в Lisp. Первое позволяет вам тратить меньше времени на убеждение компилятора в том, что вам можно запустить программу, и больше времени на её действительный запуск и работу с ней4). Последнее позволяет интерактивно разрабатывать даже код обработки ошибок.

Другим следствием того, что Lisp — "программируемый язык" является то, что, кроме возможности вносить мелкие изменения в язык, которые позволяют легче писать программы, есть возможность без труда отражать в языке значительные, новые понятия, касающиеся общего устройства языков программирования. Например, первоначальная реализация Common Lisp Object System (CLOS) — объектной системы Common Lisp, была библиотекой, написанной на самом Common Lisp. Это позволило Lisp программистам получить реальный опыт работы с возможностями, которые она предоставляла, еще до того момента, когда библиотека была официально включена в состав языка.

Какая бы новая парадигма программирования не появилась, Common Lisp, скорее всего, без труда сможет впитать её без изменений в ядре языка. Например, один программист на Lisp недавно написал библиотеку AspectL, которая добавляет Common Lisp поддержку аспектно-ориентированного программирования (AOP)5). Если будущее за AOP, то Common Lisp сможет поддерживать его без изменений в базовом языке и без дополнительных препроцессоров и прекомпиляторов6).

Как это началось?

Common Lisp – современный потомок языка программирования Lisp, придуманного Джоном Маккарти (John McCarthy) в 1956 году. Lisp был создан для "обработки символьных данных"7) и получил своё имя от одной вещи, в которой он был очень хорош: обработки списков (LISt Processing). Много воды утекло с тех пор, и теперь Common Lisp обогащён набором современных типов данных, которые вам только могут понадобиться, а также системой обработки ситуаций, которая, как вы увидите в главе 19, предоставляет уровень гибкости, отсутствующий в системах обработки исключений таких языков, как C++, Java, Python; мощной системой объектно-ориентированного программирования; несколькими особенностями, которых нет ни в одном другом языке. Как такое возможно? Что, скажите, обусловило превращение Lisp в такой богатый язык?

Маккарти был (и есть) исследователем в области искусственного интеллекта, и многие особенности, которые он заложил в первую версию, сделало этот язык замечательным инструментом для программирования искусственного интеллекта. Во время бума ИИ в 80-е Lisp оставался излюбленным языком для решения сложных проблем, как то: автоматическое доказательство теорем, планирование и составление расписаний, компьютерное зрение. Это были проблемы, требующие сложных программ, для написания которых нужен был мощный язык, так что программисты ИИ сделали Lisp таковым. Помогла и Холодная война, т.к. Пентагон выделял деньги Управлению перспективных исследовательских программ (DARPA), часть этих денег попадала к людям, занимающимся моделированием крупных сражений, автоматическим планированием и интерфейсами на естественных языках. Эти люди также использовали Lisp и продолжали совершенствовать его, чтобы язык полностью удовлетворял их потребностям.

Те же силы, что развивали Lisp, также расширяли границы и в других направлениях — сложные проблемы ИИ требуют больших вычислительных ресурсов, как бы вы их ни решали, и если вы примените закон Мура в обратном порядке, то сможете себе представить, сколь скудными эти ресурсы были в 80-е. Так что разработчики должны были найти все возможные пути улучшения производительности их реализаций языка. В результате этих усилий современные реализации Common Lisp часто включают в себя сложные компиляторы в язык, понятный машине. Хотя сегодня, благодаря закону Мура, возможно получить высокую производительность даже интерпретируемых языков, это больше не является проблемой для Common Lisp. И, как я покажу в главе 32, используя специальные (дополнительные) объявления, с помощью хорошего компилятора можно получить вполне приличный машинный код, сравнимый с тем, который выдаст компилятор C.

80-е — это также эра Lisp-машин. Несколько компаний, самая известная из которых Symbolics, выпускали компьютеры, которые могли запускать непосредственно Lisp-код на своих чипах. Так Lisp стал языком системного программирования, который использовали для написания операционных систем, текстовых редакторов, компиляторов и много чего еще, что можно было запустить на Lisp-машине.

Фактически, к началу 80-х существовало множество Lisp-лабораторий и несколько компаний, каждая со своей реализацией Lisp, их было так много, что люди из DARPA стали высказывать свои опасения о разобщённости Lisp-сообщества. Чтобы достигнуть единства, группа Lisp-хакеров собралась вместе и начала процесс стандартизации нового языка, Common Lisp, который бы впитал в себя лучшие черты существующих диалектов. Их работа запечатлена в книге Common Lisp the Language Гая Стила (Guy Steele, Digital Press, 1984) (CLtL).

К 1986 году существовало несколько реализаций стандарта, призванного заменить разобщённые диалекты. В 1996 организация The American National Standards Institute (ANSI) выпустила стандарт, расширяющий Common Lisp на базе CLtL, добавив в него новую функциональность, такую, как CLOS и систему обработки условий. Но и это не было последним словом: как CLtL до этого, так и стандарт ANSI теперь целенаправленно позволяет разработчикам реализаций экспериментировать с тем, как лучше сделать те или иные вещи: реализация Lisp содержит богатую среду исполнения с доступом к графическому пользовательскому интерфейсу, многопоточности, сокетам TCP/IP и многому другому. В наши дни Common Lisp эволюционирует, как и большинство других языков с открытым кодом: люди, использующие его, пишут библиотеки, которые им необходимы, и часто делают их доступными для всего сообщества. В последние годы, в частности, замечается усиление активности в разработке для Lisp библиотек с открытым кодом.

Так что, с одной стороны, Lisp — один из классических языков в информатике (Computer Science), базирующийся на идеях, проверенных временем8). С другой стороны, Lisp — современный язык общего назначения, с дизайном, отражающим прагматический подход к решению сложных задач с максимальной надёжностью и эффективностью. Единственным недостатком "классического" наследия Лиспа является то, что многие все еще топчутся вокруг представлений о Лиспе, основанных на определенном диалекте этого языка, который они открыли для себя в середине прошлого столетия в то время, когда Маккарти разработал Лисп. Если кто-то говорит вам, что Lisp — только интерпретируемый язык, что он медленный, или что вы обязаны использовать рекурсию буквально для всего, спросите вашего оппонента, какой диалект Lisp'а имеется в видy, и носили ли люди клёш, когда он изучал Lisp9).

Но я изучал Lisp раньше, и он не был тем, что вы описываете!

Если вы изучали Lisp в прошлом, то можете подумать, что тот Lisp не имеет ничего общего с Common Lisp. Хотя Common Lisp вытеснил большинство диалектов, от которых он был порождён, это не единственный сохранившийся диалект, и, в зависимости от того, где и когда вы встретились с Lisp, вы могли хорошо изучить один из этих, отличных от Common Lisp, диалектов.

Кроме Common Lisp, активное сообщество пользователей есть у диалекта Lisp общего назначения под названием Scheme. Common Lisp позаимствовал из Scheme несколько важных особенностей, но никогда не пытался заменить его.

Разработанный в Массачуссетском Технологическом Институте (MIT), Scheme был быстро принят в качестве языка для начальных курсов по вычислительной технике. Scheme изначально занимал отдельную нишу, в частности, проектировщики языка постарались сохранить ядро Scheme настолько малым и простым, насколько это возможно. Это давало очевидные выгоды при использовании Scheme как языка для обучения, а также для исследователей в области языков программирования, которым важна возможность формального доказательства предположений о языке.

Существовало также ещё одно преимущество: язык легко можно было изучить по спецификации. Все эти преимущества достигнуты за счёт отсутствия многих удобных особенностей, стандартизированных в Common Lisp. Конкретные реализации Scheme могут предоставлять эти возможности, но такие отклонения от стандарта делают написание переносимого кода на Scheme более сложным, чем на Common Lisp.

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

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

Двумя другими распространёнными диалектами Lisp являются ELisp, язык расширений для редактора Emacs, и Autolisp, язык расширений для программы Autodesk AutoCAD. Хотя, возможно, суммарный объём кода, написанного на этих диалектах, перекрывает весь остальной код, написанный на Lisp, оба эти диалекта могут использоваться только в рамках приложений, которые они расширяют. Кроме того, они являются устаревшими по сравнению и с Common Lisp и Scheme. Если Вы использовали один из этих диалектов, приготовьтесь к путешествию на Lisp-машине времени на несколько десятилетий вперёд.

Для кого эта книга?

Эта книга для вас, если вы интересуетесь Common Lisp, независимо от того, знаете ли вы его или просто хотите понять, из-за чего вокруг него разгорелась вся эта шумиха.

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

Если вы упёртый прагматик, желающий знать достоинства Common Lisp перед другими языками, такими, как Perl, Python, Java, C или C#, эта книга даст вам несколько идей по этому поводу. Или, может быть, вам нет никакого дела до использования Lisp и вы уверены, что он ничуть не лучше языков, которые вы уже знаете, но вам надоели заявления какого-нибудь Lisp-программиста, что вы просто не поняли его как следует. Если так, то в данной книге вы найдёте краткое введение в Common Lisp. Если после чтения этой книги вы по-прежнему будете думать, что Common Lisp ничем не лучше, чем ваши любимые языки, у вас будут веские обоснованные аргументы.

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

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

1)Perl также заслуживает изучения в качестве "изоленты для Internet".
2)К сожалению, нет достоверных исследований продуктивности для разных языков программирования. Один из отчётов, показывающих, что Lisp не уступает C++ и Java в совокупной эффективности программ и труда программистов, находится по адресу http://www.norvig.com/java-lisp.html.
3)Психологи выделяют состояние сознания, называемое потоком (поток сознания?), в котором мы обладаем немыслимой концентрацией и производительностью. Важность данного состояния при программировании была осознана в последние 2 десятилетия, с тех пор, как данная тема была освещена в классической книге о человеческом факторе в программировании "Эффективные проекты и команды" Тома Демарко (Tom DeMarko) и Тимоти Листера (Tim Lister). Два ключевых факта о состоянии потока: требуется около 15 минут, чтобы войти в него, и даже короткие прерывания могут вывести из данного состояния, после этого требуется опять 15 минут на вход в него. Демарко и Листер, как и многие последующие авторы, концентрируются на исключении прерываний, разрушающих состояние потока, таких, как телефонные звонки и неподходящие визиты начальника. Меньше внимания обращается на не менее важные вещи — прерывания из-за инструментов, которые мы используем в своей работе. Например, языки, которые требуют долгой компиляции прежде, чем вы сможете запустить ваш код, могут быть не менее губительными для потока, чем надоедливый начальник или звонки по телефону. Lisp может рассматриваться как язык, спроектированный для программирования в состоянии потока сознания.
4)Эта точка зрения противоречит некоторым распространённым мнениям. Статическая типизация против динамической — одна из классических тем для словесных перепалок между программистами. Если Вы пришли из мира С++ или Java (или из мира функциональных языков со статической типизацией, таких, как ML или Haskell) и не представляете жизни без статических проверок типов, можете пока отложить эту книгу. Но прежде чем сделаете это, вам, возможно, будет интересно узнать, что пишут о динамической типизации такие её поборники, как Мартин Фаулер и Брюс Эккель своих блогах — тут и тут. С другой стороны, люди из мира SmallTalk, Python, Perl или Ruby будут чувствовать себя как дома.
5)AspectL — интересный проект, так как его предшественника из мира Java, AspectJ, создал Грегор Кичалес (Gregor Kiczales), один из проектировщиков объектной и метаобъектной системы Common Lisp. Для многих Lisp-программистов AspectJ выглядел как попытка автора портировать идеи Lisp'а в Java. Тем не менее, Паскаль Констанца (Pascal Costanza) — автор AspectL, считает, что в AOP есть интересные идеи, которые будут полезны в Common Lisp. Конечно, он смог реализовать AspectL в виде библиотеки благодаря немыслимой гибкости Common Lisp Meta Object Protocol, разработанного Кичалесом. Для реализации AspectJ пришлось написать отдельный компилятор для компиляции нового языка в Java-код. Страница проекта AspectL находится по адресу http://common-lisp.net/project/aspectl/.
6)Или, выражаясь более технически грамотно, Common Lisp поставляется со встроенной возможностью интеграции компиляторов для встроенных языков.
7)Lisp 1.5 Programmer's Manual (M.I.T. Press, 1962).
8)Некоторые идеи, впервые реализованные в Lisp: конструкция if-then-else, рекурсивный вызов функций, динамическое распределение памяти, сборка мусора, представление функций как полноценных объектов, лексические замыкания, интерактивное программирование, инкрементальная компиляция и динамическая типизация.
9)Один из наиболее распространённых мифов о Lisp гласит, что он мёртв. Хотя Common Lisp, действительно, используется не так широко, как, скажем, Visual Basic или Java, странно называть мёртвым язык, который постоянно приобретает новых пользователей и используется для разработки новых проектов. Несколько последних случаев успешного применения Lisp — проект Viaweb Пола Грэхема, который стал впоследствии Yahoo Store, когда Yahoo купила его компанию; система заказа авиабилетов ITA Software; QPX, используемый компанией Orbitz и другими для продажи билетов он-лайн; игра "Jak and Daxter" компании Naughty Dog для PlayStation 2, написанная на специализированном диалекте Lisp GOAL, компилятор которого, в свою очередь, написан на Common Lisp; Roomba — автоматический робот-пылесос, программная начинка которого написана на L, подмножестве Common Lisp. Может быть, ещё больше за себя скажут рост объёма и популярности сайта http://Common-Lisp.net, на котором размещаются проекты на Common Lisp с открытым кодом, и стремительно возросшее в последние годы число локальных групп пользователей Lisp.
Оглавление Следующая
@2009-2013 lisper.ru