В этой статье мы поговорим об одном из самых старых языков программирования ― Lisp. Несмотря на свой внушающий уважение возраст, он всё ещё находится в строю и заставляет переосмысливать всю теорию программирования. Так что же это за язык и чем он примечателен?
Лисп, или LISP (от англ. LISt Processing language — «язык обработки списков», современное написание: Lisp) — семейство языков программирования, программы и данные в которых представляются в виде списков.
Существует альтернативная расшифровка названия LISP: Lots of Irritating Superfluous Parentheses («Много раздражающих лишних скобок») — намёк на особенности синтаксиса языка.
Шутливое «Десятое правило Гринспена» гласит: «Любая достаточно сложная программа на Си или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp».
![](https://habrastorage.org/getpro/habr/upload_files/938/12e/4c7/93812e4c7f55e8c73814e2a42b966a96.png)
Правило, несмотря на свою юмористичность, выражает важную мысль. При программировании на традиционных императивных языках разработчики тратят очень много времени на реализацию. Результат — худшее качество тех возможностей, которые функциональным языкам, в частности Лиспу, присущи изначально.
![Источник фото: https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/John_McCarthy_Stanford.jpg/1024px-John_McCarthy_Stanford.jpg Источник фото: https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/John_McCarthy_Stanford.jpg/1024px-John_McCarthy_Stanford.jpg](https://habrastorage.org/getpro/habr/upload_files/aa6/29f/fa9/aa629ffa9dacd2c5e475dcfffd0eeed4.jpg)
Создан был этот язык в конце 1950-х годов математиком Джоном Маккарти. Принято считать, что именно он положил начало понятию «искусственного интеллекта».
Во время работы над одним из своих исследовательских проектов ему пришлось разработать совершенно новый язык программирования, содержащий ряд концепций, до этого не применявшихся.
1. Условные конструкции
If/then/else и построения из них впервые появились именно в языке Lisp и только затем были позаимствованы другими языками.
2. Функции
В этом языке функции находятся на том же уровне, что и строки или числа.
3. Рекурсия
Несмотря на свою математическую природу и то, что она была известна гораздо раньше появления языка Lisp, впервые она была реализована именно в нём.
4. Переосмысление переменных
Все переменные в рамках языка Lisp представляют собой указатели.
5. Сборка мусора
Механизм эффективного автоматического контроля памяти, который стирает из неё ненужные объекты, впервые появился именно в Lisp-е.
6. Вся программа построена на основе выражений
Стандартная Lisp-программа представляет собой деревья выражений, которые могут возвращать конкретные значения.
Если язык полностью состоит из выражений, это позволяет составлять их любимым способом:
(if foo (= x 1) (= x 2))
либо
(= x (if foo 1 2))
7. Символьный тип (A symbol type)
Символы отличаются от строк, что позволяет проверить на равенство, сравнив указатели.
8. Нотация для кода (A notation for code)
Подразумевает использование деревьев из символов.
9. Весь язык всегда доступен (The whole language always available)
Нет явного различия между временем чтения, компиляции и выполнения. Можно компилировать или запускать код во время чтения, читать или запускать кода, пока идёт процесс компиляции, или читать, или компилировать код во время выполнения.
Чем ещё примечателен этот язык? Дело в том, что каждый язык обладает развитой библиотекой инструментов для реализации многих концепций. Любой практикующий программист может воспользоваться либо уже имеющимися в наличии классами, переменными, константами и функциями, либо написать свои собственные реализации.
Lisp же, помимо этого, предоставляет возможности расширения синтаксиса, для чего в нём реализована система макросов. Например, Пол Грэм называет макросы «программами, которые пишут программы». Есть мнение, что Lisp более гибкий, чем обычные языки программирования, так как позволяет неожиданным и творческим образом сочетать элементы, что недоступно никакому другому языку.
Например, рассмотрим минимальную исполняемую программу, которая компилируется и выполняется, даже если при этом ничего существенного не происходит. Начнём с C/C++. В этих языках «точкой входа» — местом, где программа начинает выполняться — является функция main (что значит «основная»):
main(){}
Круглые скобки после имени функции указывают на то, что мы определяем функцию, а не переменную (подробнее об этом позже), фигурные скобки содержат тело функции, инструкции, которые будут выполнены, когда функция будет вызвана. Пока оставим тело функции пустым. На самом деле написанный выше код упрощён. Он будет компилироваться и выполняться, но так определять можно только единственную функцию — main. Дело в том, что всякая функция имеет тип (это он совпадает с типом вычисляемого результата). Если функция ничего не возвращает, её тип void («ничто»), и он обязательно указывается перед именем функции. Если же тип функции не указан, то подразумевается тип int (сокращение от integer, что значит «целый», т. е. целое число), и тогда функция обязана возвращать целое значение инструкцией return. Функция main имеет целый тип, так что полная версия будет такой:
int main()
{
return 0;
}
Напишем то же самое на Java. Как и в C/C++, точкой входа является функция main, но в Java не существует функций вне классов. Таким образом, нам нужно определить класс, членом которого является функция main (функция, принадлежащая классу, называется методом):
public class Minimal {
public static void main(String[] args) {}
}
В первой строке мы определили класс по имени Minimal, во второй строке мы определили пустой метод main. В отличие от C/C++, в Java все ключевые слова обязательны при определении классов (public class) и методов (public static void). Кроме того, в Java метод main всегда получает массив строк (об этом позже).
Теперь напишем то же самое на Lisp:
T
Это не опечатка. Вся программа состоит из единственной буквы T, которая в Lisp означает истинное значение (сокращение от truth — «истина»). Что же произошло? Во-первых, в Lisp всякое выражение возвращает некоторый результат. Даже если выражение не является вызовом функции и ничего не вычисляет. В частности, любое значение вычисляется само в себя. Во-вторых, точкой входа является то, что пользователь запускает, будь то функция или значение. Мы «запустили» истину, и она, ничего не вычислив, возвратила саму себя.
Жемчужины Lisp:
не обязывает программиста определять элементы программы (функции, классы и др.) без необходимости;
любая сущность Lisp может быть запущена. При этом она возвращает осмысленный результат. В частности это означает, что Lisp не делает разницы между выражениями (expression) и инструкциями (statement), его синтаксис не нуждается в специальном символе завершения инструкции, типа ; в C/C++/Java или разделения между инструкциями в Pascal.
Говоря об этом языке, нельзя не вспомнить и о макросах. Макрос в Lisp — это своего рода функция, которая получает в качестве аргументов формы или объекты и, как правило, генерирует код, который затем будет скомпилирован и выполнен. Это происходит до выполнения программы во время фазы, которая называется развёрткой макросов (macroexpansion). Проще говоря, с помощью макросов можно сделать так, чтобы код Lisp действовал как любой другой язык программирования… Воистину поразительно!
Именно поэтому он получил большую популярность среди хакеров в своё время. Многие отмечают ещё и то, что преимущества этого языка далеко не всегда очевидны тем, кто пытается его использовать, однако более умудрённые программисты говорят, что основной его пользой является опыт переосмысления информатики и программирования в целом. Новый опыт получает любой программист, столкнувшийся с Lisp.
Так как Lisp основан на списках, оператор может представлять собой список элементов, каждый из которых может быть представлен тоже списком либо конечным неделимым элементом, получившим название «атом». В качестве такого атома может выступать, например, какое-либо значение в виде числа или символа.
Lisp является языком системного программирования для так называемых лисп-машин, производившихся в 1980-е годы, например, фирмой Symbolics.
Lisp-машина — универсальная вычислительная машина, архитектура которой оптимизирована для эффективного выполнения программ на языке Lisp.
Эквивалентна абстрактной машине Тьюринга (и обычному персональному компьютеру) по критерию полиноминальной сводимости.
Несмотря на то, что лисп-машины никогда не были широко распространены, многие популярные сейчас идеи и программные технологии были впервые разработаны с помощью лисп-машин. Так, на машинах, которые использовались в исследовательском центре Xerox PARC, были реализованы:
сборка мусора;
лазерная печать;
многооконный графический интерфейс пользователя;
растровая графика высокого разрешения;
рендеринг;
множество сетевых инноваций.
Lisp-машины предоставляли широкие возможности по проведению экспериментальных разработок в области компьютерных наук. На базе разработок таких машин было создано новое поколение инженерных рабочих станций.
Любой язык программирования является живой субстанцией, которая постоянно развивается и совершенствуется. Не был исключением и язык Lisp. Так, ряд лабораторий, исследовавших вопросы искусственного интеллекта, начали предлагать свои интерпретации языка. В результате это привело к появлению ряда диалектов языка Lisp. В период с 1960-х по 1980-е годы образовались такие диалекты, как MacLisp, Interlisp, PSL, Franz Lisp, Scheme, Zetalisp, NIL и T.
Стандартизация языка
К первой половине 1980-х годов в лисп-сообществе сложилась ситуация, которую некоторые авторы сравнивали с Вавилонской башней: параллельно существовали и развивались более десятка крупных диалектов Лиспа, общее же число несовместимых между собой реализаций было существенно больше. Похожая ситуация наблюдалась в это время в большинстве распространённых языков программирования, в случае же с Лиспом ситуация усугублялась тем, что язык изначально был разработан как произвольно расширяемый, что спровоцировало развитие его возможностей в разных диалектах в существенно разных направлениях.
На начальном этапе, когда Lisp использовался почти исключительно в лабораториях и институтах, многообразие диалектов не сильно мешало и даже было отчасти полезным, поскольку способствовало быстрому развитию языка. Но к 1980-м годам, когда появилась потребность в промышленных разработках на Лиспе, обилие реализаций стало тормозом, так как приводило к массовому дублированию разработок и рассредоточению сил на поддержку множества лисп-систем.
Попытки стандартизации Lisp предпринимались почти с момента его появления (первое предложение по стандартизации датируется 1960 годом), но из-за разобщённости и значительных различий в потребностях заинтересованных групп разработчиков ни одно из предложений не было принято. Во второй половине 1970-х годов Министерство обороны США провело огромную работу по анализу ситуации в программных разработках военного назначения, после чего организовало конкурс на разработку нового языка высокого уровня для встроенных систем, которым стал язык Ада. Однако Ада изначально не предназначалась для искусственного интеллекта и символьной обработки, вследствие чего для таких разработок военное ведомство США оказалось вынуждено допустить к использованию более подходящий язык. Поэтому Министерство обороны США оказало организационную и финансовую поддержку формированию промышленного стандарта языка, который и приняло в качестве дополнительного средства разработки ПО для военных применений.
Первоначальный вариант стандарта начали готовить в Университете Карнеги — Меллона на основе внутреннего проекта Spice Lisp, также первоначально нацеленного на разработку лисп-системы для рабочей станции. Проектируемый стандарт с самого начала получил наименование Common Lisp («Общий Lisp»), подчёркивающее цель разработки — получить единый базовый язык, на основании которого можно было бы создавать программно-совместимые системы. В разработке и редактировании стандарта приняли участие около 80 специалистов из университетов, лабораторий и фирм США. Процесс разработки впервые происходил дистанционно, с помощью компьютерной сети ARPANET, через которую было передано свыше 3000 сообщений. Процесс разработки стандарта завершился в 1984 году. Его результат был зафиксирован в первом издании руководства Common Lisp: the Language Гая Стила.
Для чего применяется
Сферы применения языка Lisp многообразны: наука и промышленность, образование и медицина, от декодирования генома человека до системы проектирования авиалайнеров. Первые области применения языка Лисп были связаны с символьной обработкой данных и процессами принятия решений. Наиболее популярный сегодня диалект Common Lisp является универсальным языком программирования. Он широко используется в самых разных проектах: интернет-серверы и службы, серверы приложений и клиенты, взаимодействующие с реляционными и объектными базами данных, научные расчёты и игровые программы.
Существуют специализированные диалекты Lisp, предназначенные для конкретных применений, например, Game Oriented Assembly Lisp (GOAL). Он создан для написания высокодинамичных трёхмерных игр, на нём целиком написана серия игр Jak and Daxter.
Одно из направлений применения Lisp — его использование в качестве скриптового языка, автоматизирующего работу в ряде прикладных программ, в том числе:
AutoLISP — скриптовый язык САПР AutoCAD;
Emacs Lisp — встроенный язык текстового редактора Emacs, использованный как в реализации самого редактора, так и в разработке дополнений к нему, что даёт неограниченные возможности расширения функциональности;
Interleaf Lisp — скриптовый язык в издательском программном обеспечении Interleaf/Quicksilver;
Nyquist — скриптовый язык в аудиоредакторе Audacity;
Rep (близок к Emacs Lisp) — язык настроек и расширений в оконном менеджере Sawfish;
SKILL — скриптовый язык САПР Virtuoso Platform компании Cadence Design Systems;
TinyScheme — один из скриптовых языков в свободном графическом процессоре Gimp версии 2.4 или более;
ICAD — система «знаний на основе знаний», которая позволяет пользователям кодировать знания дизайна и опыт инженерного проектирования.
Что дальше?
В случае Лиспа сложно провести чёткую грань между диалектом и языком-потомком, так как различные диалекты Лиспа, созданные за более чем полвека его существования, могут существенно различаться и быть несовместимыми. С другой стороны, Лисп просто в силу возраста оказал то или иное влияние на огромное число языков, причём не только функциональных. Если считать прямыми потомками Лиспа только языки, сохранившие общую структуру программы, но синтаксически несовместимые с Лиспом, то можно выделить следующие:
Scheme — разработанный в 1976 вариант Lisp, который и сегодня используется в обучении программированию и в исследовательских целях, а также применяется в качестве встраиваемого языка.
Racket — потомок Scheme, разрабатываемый с 1994 года и находящийся в использовании по сей день. Мощная расширяемая lisp-система, включающая в себя все современные средства поддержки программирования и большой массив библиотек.
Clojure — созданный в 2007 году на основе Lisp язык функционального программирования, интегрированный с платформой Java (программы транслируются в байт-код и работают под управлением JVM). Унаследовав основные черты Lisp, язык имеет целый ряд синтаксических отличий и нововведений. Интеграция с Java-платформой даёт возможность непосредственно применять весь массив накопленных библиотек для данной платформы. Также Clojure имеет встроенную поддержку параллельного программирования, причём является одним из немногих языков, поддерживающих механизм транзакционной памяти.
Лого — язык и интерактивная среда, разработанные в 1967 году Сеймуром Пейпертом и Идит Харель для обучения детей дошкольного и младшего школьного возраста основным концепциям программирования. Язык имеет лисп-подобный списочный синтаксис, в котором устранена необходимость использования большинства скобок. Поддерживается также и императивная форма программы, напоминающая Бейсик. Повторение, кроме рекурсии, может быть реализовано с помощью конструкции цикла с фиксированным числом итераций. Характерная особенность среды интерпретатора Лого — поддержка визуального агента («черепашки»), изображаемого в виде пиктограммы на графическом поле (в окне).
Завершая рассказ, можно сказать, что Lisp до сих пор остаётся одним из основных использующихся языков. Применяется он и как средство обычного промышленного программирования, от встроенных скриптов до веб-приложений массового использования, хотя популярным его назвать нельзя: в рейтингах популярности языков он стабильно занимает примерно 29-30 места.
Комментарии (17)
sergio_nsk
14.03.2022 21:08+3Теперь напишем то же самое на Lisp:
T
Не совсем уместно сравнивать минимальные программы компилируемых языков программирования и скриптовых. Правильнее было бы сравнивать с python, bash, JavaScript, и тут, внезапно, минимальная программа на Lisp проигрывает.
punzik
14.03.2022 22:13+1Так лисп вполне компилируемый. Например, SBCL.
sergio_nsk
14.03.2022 22:22+1Наличие компилятора не отменяет факт интерпретируемости языка программирования. Из примеров выше: python, JavaScript.
andreyorst
16.03.2022 20:18+1А что подразумевается под "интерпретируемостью" языка? SBCL компилит common lisp в нативный код:
CL-USER> (disassemble '(lambda () T)) ; disassembly for (LAMBDA ()) ; Size: 21 bytes. Origin: #x52CF77AC ; (LAMBDA ()) ; AC: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer ; B0: 488945F8 MOV [RBP-8], RAX ; B4: BA4F001050 MOV EDX, #x5010004F ; T ; B9: 488BE5 MOV RSP, RBP ; BC: F8 CLC ; BD: 5D POP RBP ; BE: C3 RET ; BF: CC10 INT3 16 ; Invalid argument count trap NIL CL-USER> (disassemble '(lambda () (declare (optimize (speed 3) (safety 0))) T)) ; disassembly for (LAMBDA ()) ; Size: 11 bytes. Origin: #x52CF7A26 ; (LAMBDA ()) ; 26: BA4F001050 MOV EDX, #x5010004F ; T ; 2B: 488BE5 MOV RSP, RBP ; 2E: F8 CLC ; 2F: 5D POP RBP ; 30: C3 RET NIL
Лисп это компилируемый язык, с единицей компиляции форма, а не файл, как в большинстве других языков. Безусловно, есть и чисто интерпретируемые диалекты, но в основном они либо компилятся в байткод и потом JIT компилит это в натив, либо сразу компилятся в натив.
vics001
15.03.2022 02:42Несмотря, на то, что я большой поклонник Пролог и декларативного программирования. Считаю, что проблема Лисп (самого чистого и простого функционального языка) и Пролога, в том, что они не алгоритмичны, а академичны.
Они заставляют думать и писать по-другому, то есть простой набор инструкций или цикл, превращается в рекурсию или в написание алгоритмического языка на Лиспе (Прологе) для написания самого алгоритма.
Побуду занудой, но любой уважающий себя программист, должен уметь написать функции высшего порядка, свой парсер и оптимизировать хвостовую рекурсию.
andreyorst
16.03.2022 23:20Пролог, возможно, академичен, видел его всего пару раз во время учебы в университете, но более не прикасался. А вот насчет академичности лиспа не соглашусь)
Считаю, что проблема Лисп (самого чистого и простого функционального
языка) и Пролога, в том, что они не алгоритмичны, а академичны.Лисп не самый чистый и в общем-то не функциональный. Разные диалекты в этом плане могут отличаться, к примеру тот же Clojure делает большую функциональный стиль, а Common Lisp является мультипарадигмным языком, с общирными возможностями как в ООП так и в ФП парадигмах. Scheme больше полагается на функциональный стиль, но при этом обладает и императивными возможностями. А насчет академичности - обыкновенный код, как и на других языках, просто выглядит подругому и исторически имеет более удобные символьные вычисления, чем во многих других языках, так как проектировался с учетом такой потребности.
Они заставляют думать и писать по-другому, то есть простой набор
инструкций или цикл, превращается в рекурсию или в написание
алгоритмического языка на ЛиспеНа самом деле нет. Вы можете писать на лиспе как и на любом другом императивном языке, тут это скорее зависит от идиом и основного фокуса конкретного диалекта.
Про итерацию - Clojure предлагает набор макросов и специальных форм для итерации:
loop
,for
,while
, не говоря уже о более функциональныхmap
илиreduce
. Никакой рекурсии (за исключением,loop
, но там это частный случай). Common Lisp имеет свой, довольно уникальный макросloop
, который предлагает очень мощный DSL для выражения итерации любого вида.Подобное суждение скорее всего связано с тем, что вы видели, что в условном SICP всё решается через рекурсию, что на самом деле является довольно неплохой разминкой для ума, потому и использовано в книге, а в реальном коде рекурсия чаще встречается там, где она нужна, а не везде, где нужна итерация)
oleg_shamshura
15.03.2022 08:27+3Однажды в 80х Джон Маккарти приехал в Новосибирский Академгородок и посетил Международную школу юных программистов в Речкуновке. Я даже постоял рядом с ним -- минут пять, с отвисшей (из уважения!) челюстью. Он подарил школе калькулятор, работающий на LIPS -- раскрывающийся, как книжка. Открыл я этот калькулятор, написал 2 + 2, он мне ответил Error, я его закрыл и благоговейно положил на место. С тех пор знаю, что LISP крут.
skymorp
15.03.2022 09:03+12. Функции
В этом языке функции находятся на том же уровне, что и строки или числа.
Мне кажется что человек не в теме совершенно не поймет о чем идет речь. Вероятно, стоило упомянуть что это First-class function.
Аналогично для п.6 (возможно стоило сказать что делает этот код).
Итого (имхо): непонятно для кого статья.
Ustas4
15.03.2022 18:54Если не считать учебу в институте на курсе фортрана, то автолисп в 80х открыл мне дорогу в IT сегодня
forthuser
15.03.2022 19:57А, после этого, он остался в дальнейшей практике, хотя бы, и личного использования?
forthuser
Язык программирования Lisp уроки и задания (видео уроки)
ЛШЮП-2020
P.S. Перевод цикла статей. (1-10)
Lisp: Слезы радости, часть 1 rus-linux.net/MyLDP/algol/LISP/lisp01.html
…