Comments 26
ЛШЮП-2020
P.S. Перевод цикла статей. (1-10)
Lisp: Слезы радости, часть 1 rus-linux.net/MyLDP/algol/LISP/lisp01.html
…
Когда в статье о LISP очень много скобочек даже в тексте.
Теперь напишем то же самое на Lisp:
T
Не совсем уместно сравнивать минимальные программы компилируемых языков программирования и скриптовых. Правильнее было бы сравнивать с python, bash, JavaScript, и тут, внезапно, минимальная программа на Lisp проигрывает.
Так лисп вполне компилируемый. Например, SBCL.
Наличие компилятора не отменяет факт интерпретируемости языка программирования. Из примеров выше: python, JavaScript.
Для Си тоже есть интерпретатор. Он тоже скриптовый?
Ну вот зачем быть таким занудой.
Практически все известные мне (которые щупал) системы Scheme это компиляторы. mzscheme, racket, chicken, bigloo, gambit, guile, stalin, ...
То же касается Common Lisp.
То, что в комплекте у некоторых поставляется интерпретатор не делает язык интерпретируемым.
И да, минимальная программа (из T, в случае CL или #t, в случае scheme) прекрасно компилируется и выполняется.
А что подразумевается под "интерпретируемостью" языка? 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 компилит это в натив, либо сразу компилятся в натив.
В тексте подразумевался не способ исполнения (интерпретируемый/компилируемый) - а то, что существует ряд диалектов языка Lisp.
"С момента появления, различные лаборатории, исследовавшие вопросы искусственного интеллекта начали предлагать свои интерпретации языка. В конечном счете, это привело к появлению ряда диалектов языка Lisp.
В период с 1960-х по 1980-е годы появились диалекты MacLisp, Interlisp, PSL, Franz Lisp, Scheme, Zetalisp, NIL и T."
Сейчас в моде не черепашки, а коты: Лого может и хорош, его учить детей приходится Скретч, ибо для практической пользы и скорейшей прокачки программерюгенд нужно мейнстримное ООП.
Несмотря, на то, что я большой поклонник Пролог и декларативного программирования. Считаю, что проблема Лисп (самого чистого и простого функционального языка) и Пролога, в том, что они не алгоритмичны, а академичны.
Они заставляют думать и писать по-другому, то есть простой набор инструкций или цикл, превращается в рекурсию или в написание алгоритмического языка на Лиспе (Прологе) для написания самого алгоритма.
Побуду занудой, но любой уважающий себя программист, должен уметь написать функции высшего порядка, свой парсер и оптимизировать хвостовую рекурсию.
Пролог, возможно, академичен, видел его всего пару раз во время учебы в университете, но более не прикасался. А вот насчет академичности лиспа не соглашусь)
Считаю, что проблема Лисп (самого чистого и простого функционального
языка) и Пролога, в том, что они не алгоритмичны, а академичны.
Лисп не самый чистый и в общем-то не функциональный. Разные диалекты в этом плане могут отличаться, к примеру тот же Clojure делает большую функциональный стиль, а Common Lisp является мультипарадигмным языком, с общирными возможностями как в ООП так и в ФП парадигмах. Scheme больше полагается на функциональный стиль, но при этом обладает и императивными возможностями. А насчет академичности - обыкновенный код, как и на других языках, просто выглядит подругому и исторически имеет более удобные символьные вычисления, чем во многих других языках, так как проектировался с учетом такой потребности.
Они заставляют думать и писать по-другому, то есть простой набор
инструкций или цикл, превращается в рекурсию или в написание
алгоритмического языка на Лиспе
На самом деле нет. Вы можете писать на лиспе как и на любом другом императивном языке, тут это скорее зависит от идиом и основного фокуса конкретного диалекта.
Про итерацию - Clojure предлагает набор макросов и специальных форм для итерации: loop
, for
, while
, не говоря уже о более функциональных map
или reduce
. Никакой рекурсии (за исключением, loop
, но там это частный случай). Common Lisp имеет свой, довольно уникальный макрос loop
, который предлагает очень мощный DSL для выражения итерации любого вида.
Подобное суждение скорее всего связано с тем, что вы видели, что в условном SICP всё решается через рекурсию, что на самом деле является довольно неплохой разминкой для ума, потому и использовано в книге, а в реальном коде рекурсия чаще встречается там, где она нужна, а не везде, где нужна итерация)
2. Функции
В этом языке функции находятся на том же уровне, что и строки или числа.
Мне кажется что человек не в теме совершенно не поймет о чем идет речь. Вероятно, стоило упомянуть что это First-class function.
Аналогично для п.6 (возможно стоило сказать что делает этот код).
Итого (имхо): непонятно для кого статья.
Надо было ещё рассказать о практике применения Clojure в Сбере.
Если не считать учебу в институте на курсе фортрана, то автолисп в 80х открыл мне дорогу в IT сегодня
Спасибо за статью и в целом, что вспомнили про Lisp. Но хотелось бы внести ясность по тому, как сейчас обстоят дела.
Напрочь забыты реализации Common Lisp. Он жив, жил и будет жить. Это один из самых богатых представителей lisp-family как со стороны батареек (за исключением Clojure), так и по количеству реализацией. Есть и платные, есть и свободные. На любой вкус.
Если говорить о Scheme, то тут надо брать конкретную реализацию. Она не одна и есть нюансы (в отличии от CL по ощущениям их больше). А ведь ещё есть и экосистема Guix, которая построена на нём (ЕМНИП, кто пользуется - поправьте), и NixOS построенная на ней. Об этом как-то забыли.
Racket - изначально был диалектом Scheme, потом стал отдельным языком в lisp-family (он очень далеко уже ушёл от Scheme). От него был отпочковался такой диалект как Typed Racket и ещё несколько. Racket как и Scheme ближе к ФП, чем тот же Common Lisp, некоторые диалекты Racket являются в целом прямым конкурентом тому же Haskell, но с сохранением особенностей семейства. Одним из отличий является наличие гигиенических макросов и в целом направленность Racket на решение задач через разработку DSL. Ну и да, не только в Clojure все хорошо с асинхронностью и параллелизмом. В Racket как минимум не хуже.
Clojure - самый спорный представитель всего lisp-family. Многие считают, что он не относится к лиспам вообще, а лишь походит на них синтаксически. В частности, в Clojure сильно обрезана система макросов, немного по другому строится в работа самого языка (всё же на JVM). Объектная модель с мультиметодами - тоже уникальная особенность языка, она не использует мета-объектный протокол. Ну и для lisp-family в нём очень много синтаксиса, который не является частью экосистемы а просто есть как сахар. Нет и некоторых вполне дефолтных типов данных. Все лиспы можно описать как (a(b(c))). С Clojure этого не пройдёт.
Если говорить о JVM - ABCL и Kawa все же больше лиспы, чем Clojure. И заслуживают внимания не меньше, если не больше.
Напрочь забыт PicoLisp, который является языком и СУБД в одном, развивается небольшим, но очень уютным комьюнити. И как раз он сейчас, ИМХО, наиболее интересен как "язык = платформа".
Есть реализация Lisp для виртуальной машины Erlang, которая пока только в зайчаточном состоянии, но уже представляет интересный проект. И если сравнивать с Clojure имеет куда больше фишек по параллельным и асинхронным операциям, да ещё и может использовать OTP.
Если говорим об Emacs и EmacsLisp, то тут скорее правильно говорить как о платформе. Сам по себе Emacs ближе к ОС, язык которой и выступает EmacsLisp. Собственно, на Emacs можно реализовать хоть веб-сервер, хоть CRM, хоть игровой движок. Проблема основная в изначальной не общей направленности. Но всё же. Язык и платформа - живы и переживут всякие Eclipse или поделия JetBrains.
А еще есть далекие потомки, такие как OpenDylan (не взлетевший но развиваемый язык от огрызочной компании, лисп без скобок) или JS (внезапно, это переделанная сильно "схема" для браузера).
Ну и на последок, не раскрыта в статье самые главные особенности lisp-family, которые обеспечивает языку такой почтительный срок жизни - "код есть данные" и "кровавый патчинг". Любой настоящий lisp способен воспринимать изменения кодовой базы и обрабатывать код, мутировать его. Он является одним из лучших вариантов для построения систем, которые пишут другие системы. Динамически, прямо в рантайме. ЕМНИП, ближе всех к этому из академических языков подошёл РЕФАЛ, но он уже закопан очень глубоко. Более или менее то же самое предлагал TCL, но он давно не развивается.
Не холивара ради, но по абзацу с Clojure у меня возникло несколько вопросов и уточнений.
В частности, в Clojure сильно обрезана система макросов
Интересно было бы подробнее почитать про это. В сравнении с Common Lisp макросами я особых различий не заметил. В схемах, совершенно отдельная система макросов, поэтому с ней сравнивать наверное некорректно. Конечно, я не писал большого количества макросов на CL, так что могу чего-то не знать, но в Clojure (и некоторых других диалектах) с макросами у меня довольно большой опыт, и там макросы это те же самые квазиквоты с анквотами, с манипуляцией теми же самыми списками теми же самыми операциями, с единственным различием в резолвинге имён в квазиквотировании, т.к. он сделан немного иначе. В общем если есть какая-то конкретная литература по различиям, было бы интересно взглянуть!) Про отсутствие ридер макросов знаю, в Clojure их намерено не стали добавлять, но добавили tagged literals, что дает некоторые возмоности довольно близкие к ридер макросам. Не то же самое, конечно, но и цели такой не стояло.
Объектная модель с мультиметодами - тоже уникальная особенность языка, она не использует мета-объектный протокол.
Это не объектная модель и она отнюдь не уникальна. Это обыкновенный рантайм диспатч на основе значения, делается довольно легко в многих других языках. Насчет MOPа, да в Clojure этот протокол не используется, но Clojure вообще спроектирован иначе и не предполагает объектно-ориентированного подхода за пределами взаимодействия с его хост платформой, где MOP так же не используется. Вместо ООП упор делается на функциональную парадигму с чистыми функциями оперирующими неизменяемыми данными. Есть и диспатч на основе протоколов, что очень похоже на generic функции из CL, с той разницей что работает за счет реализации этого хост платформой.
Нет и некоторых вполне дефолтных типов данных.
Например? В Clojure есть всё, что есть в Java, соответственно всё основные типы данных доступные в хост платформе доступны и в Clojure. А сам язык предлагает персистентные варианты для всех основных коллекций - множества, векторы, односвязные списки, консы, ассоциативные словари и рекорды. И для всех свои литералы, что наоборот делает чтение кода более удобным и вовсе не является сахаром, так как такие литералы вычисляются на этапе чтения, а не на этапе исполнения. Т.е. `{:a 1 :b 2}` это не просто сахар для `(hash-map :a 1 :b 2)` - у них разные семанитики построения коллекции.
Да, согласен с вами про clojure и CL они на самом деле эквивалентны по функциональности, но как раз отсутствие ридер-макросов очень сильно вносит корректировки в применение их (tagged тут не полная замена всё же). Ну и да, макросы в Clojure сразу принадлежат к какому-то неймспейсу (что скорее хорошо, но немного ограничивает).
Про MOP и ООП - я тут скорее имел ввиду, что в Clojure модель не уникальная для языков в целом (вполне дефолтно на самом деле для ФП языка), а вот для Lisp (который всё же не только Lisp-1) уже не такой стандартный вариант. На сколько помню в Scheme она вообще на основе hashtables сделана. И не могу сказать, что это хорошо. Но тема скорее холиварная и касается вкуса фломастеров.
Про типы данных - для примера pair (как в том же CL), boxed integers (нет полного набора числовых типов), нет сигнального протокола исключения (хоть это и не совсем к этому, но всё же).
Так же к отличием Clojure от CL, Scheme можно отнести: case-sensitive, множество специальных типов данных (литералы, вектора, отображения, регулярные выражения, анонимные функции и т.д.), множество добавочного синтаксиса и отличий в синтаксисе (банально в том же let), другой flow исполнения (насколько помню связывание в let работает так же как let* в Scheme), нет части стандартных функций (car, cdr, etc.), nil по другому себя ведёт, поддержаны ленивые вычисления... Отличий на самом деле от общелиспа и схемы - много. Я не говорю, что они плохие, но они есть. И существенные.
Clojure я бы назвал неким Lisp-1.5, который более production/enterprise ready. Но от этого всё вроде и похоже, но не так.
З.Ы.: против Clojure ничего не имею, писал на нём какое-то время свои pet projects. Но вернулся в итоге на CL из-за всех этих нюансов.
Спасибо за подробный ответ)
Насчет многого согласен, скажу лишь, что я воспринимаю Clojure как не еще одну реализацию LISP, которой он как раз не является, а скорее как самостоятельный язык в семействе Lisp, который вобрал в себя многие лучшие черты и при этом посторался сделать что-то своё, за что мне собственно и понравился.
Насчет ридер макросов в CL знаю что большинство их избегает так как они добавляют сложностей при взаимодействии с чужим кодом и это было основой причиной их отсутствия (ну и то, что у Рича были свои планы на всякие другие символы помимо скобочек).
Кстати про сигналы и систему условий - есть отличная библиотека farolero, реализующая все основные примитивы системы условий из CL. Безусловно, так как она не интегрирована в язык, а тем более в платформу, она не решает всех проблем JVM'ных эксепшенов, но сделана достаточно прозрачно чтобы её можно было интегрировать в проекты или использовать в библиотеках, даже если их пользователь ничего не знает о такой сигнальной модели (а если знает, то может пользоваться)
Приятно было отвечать, человеку интересующемуся лиспами. На самом деле в РФ это не такое частое явление. Почему-то у нас считают язык мёртвым (хотя по ощущениям в мире он переживёт многие хайповые ЯП, просто потому что даёт всё тоже самое уже давно).
Да, согласен. Clojure и выделяется часто как отдельное семейство в lisp-family (некоторые просто его выносят вообще из семейства и говорят, что это просто "лисп подобный язык"), который имеет свои особенности, семантику и возможности. Сравнение с общелиспом его... тут скорее а надо ли? Они решают разные задачи. Clojure скорее язык для систем с высокой нагрузкой, где надо работать с большим количеством подключений и операций (тут видимо его ближайшие конкуренты Scala, Erlang, Haskell). А Common Lisp все жё язык общего назначения, который в целом для написания любого софта подходит, но делает это хуже чем специализированные языки.
За библиотеку спасибо. Посмотрю, заинтриговали. Хотя использование Clojure для меня осложнено в pet projects в первую очередь скоростью работы. Банальная авторизация с выпуском JWT занимает ЕМНИП 500-700ms на локалхосте. Это не такая частая операция (да и может я криворук), но CL раза в полтора-два с половиной быстрее при том же кейсе.
З.Ы.: про ридер-макросы. Избегают в общелиспе их скорее потому, что мало кто умеет применять и это задача далеко не для junior'a. Да и сама библиотека макросов должна проектироваться, а не появляться стихийно. Но проблема есть, да. Убрать их с одной стороны - уберечь людей от некоего пула ошибок, который очень сложно дебажить потом. А с другой - убрать большую часть свободы, которая и есть основная фича лиспов (можно сказать, что код как данные - не полностью реализуется без этого). Поэтому что наличие фичи, что её отсутствие - спорные решения.
Первый древнейший: в чём уникальность языка программирования LISP