Комментарии 63
Потому хотелось бы аргументов почему лучше выбрать именно LISP, а не F# или Erlang?
PS функционально развлечься можно и на Logo и на unLambda.
У меня на CL написано что-то типа 1С. И CL — это не функциональный а мультипарадигмальный язык программирования.
А вот «зачем?» — да, вопрос.
Хорошо язык тем, что сам язык — суть AST, всё остальное сделано на нём самом же (ну, почти). Когда начинаешь «дмать на лисп», сначала обретаешь ясное понимание, как всё вокруг работает, и далее, если интересно — никаких ограничений.
Ломать парадигмы в голове, на мой вкус лучше хаскелем (и потом на хаскеле тоже можно писать всё). По крайней мере, после сотен тысяч строк кода, что мне довелось читать за свою жизнь, очень хочется, что бы авторы были не ближе к машине, а ближе к сути прикладной задачи.
Кажется, что написать интерпретатор лиспа в любой нотации — это такой проект-на-выходные. Не удивлюсь, если внезапный 'npm install js-notated-lisp' вытащит пакет и установит. Хотя это скорее троллейбус из буханки хлеба…
Мне кажется, кому сейчас нужен современный клевый лисп — все на closure перешли.
[] это список, {} это множество. Добавьте туда функции и будет, то что надо.
Почти так устроен, например, Wolfram Language. Квадратные скобки в нём — это список. Причем для того, чтобы не пугать людей, первый элемент списка пишется перед первой квадратной скобкой. И получается почти привычное f[x]
вместо [f, x]
. Люди пользуются и не подозревают, что пишут практически на Лиспе. :)
Фигурные скобки тоже есть, но это синтаксический сахар для списка, у которого первый элемент — слово List
. Это что-то вроде пометки, что список содержит данные. Да и вообще, в Wolfram Language практически все — это сахар над списками.
Это старая тема S-expression vs M-expression. История говорит, что LISP начали создавать, имея планы ввести M-expressions для пользователей, но S-expressions внутри, но в итоге остановились на S-expressions везде (и, я бы сказал, правильно).
Сейчас M-expressions ещё родное средство для Kxʼовых k и q.
… в результате у программистов на других языках задача решена, а программисты на LISP только закончили делать DSL?
Конечно же Лисп имеет смысл применять там, где не хватает готовых решений из традиционных ЯП. Но лично меня удивляет такое внимание именно к неуклюжему Common Lisp. Почему не более изящный Scheme? Добавьте его интерпретатор к тому инструментальному средству, которое востребовано в вашем рабочем проекте (на C/C++, C#, Java или ином «скучном» языке), и у вас будет вполне достойное компромиссное решение.
Раз уж мы тут про DSL говорим, то при чём тут скобки?
Вот тебе пример из одной библиотеки которую я некоторое время назад написал. Она упрощает парсинг опций командной строки и большую часть работы делает за тебя. Прикладной код выглядит так:
(defmain main ((debug "Show traceback instead of short message."
:flag t)
(log "Filename to write log to.")
(token "GitHub personal access token."
:env-var "TOKEN")
&rest repositories)
"Utility to analyze github forks."
;; Making real work
(loop for repository in repositories
do (analyze repository
:log log
:debug debug
:token token)))
Всё, что ты видишь до строчки Making real work
, это DSL для описания функции, принимающей параметры командной строки. Под капотом оно разворачивается в такой вот код:
(progn
(net.didierverna.clon:defsynopsis (:postfix "REPOSITORY")
(defmain/defmain::text :contents "Utility to analyze github forks.")
(defmain/defmain::flag :long-name "help" :env-var nil :description
"Show help on this program." :short-name "h")
(defmain/defmain::flag :long-name "debug" :env-var nil :description
"Show traceback instead of short message." :short-name "d")
(defmain/defmain::stropt :long-name "log" :env-var nil :description
"Filename to write log to." :short-name "l")
(defmain/defmain::stropt :long-name "token" :env-var "TOKEN" :description
"GitHub personal access token." :short-name "t"))
(defun main (&rest defmain/defmain::argv)
(net.didierverna.clon:make-context :cmdline
(cons "cl-info" defmain/defmain::argv))
(let ((repository (net.didierverna.clon:remainder))
(net.didierverna.clon:help
(net.didierverna.clon:getopt :long-name "help"))
(debug (net.didierverna.clon:getopt :long-name "debug"))
(log (net.didierverna.clon:getopt :long-name "log"))
(token (net.didierverna.clon:getopt :long-name "token")))
(when net.didierverna.clon:help
(net.didierverna.clon:help)
(uiop/image:quit 1))
(handler-bind ((t
(lambda (condition)
(uiop/image:print-condition-backtrace condition :stream
*error-output*)
(uiop/image:quit 1))))
(loop for reporitory in (remainder)
do (analyze repository :log log :debug debug :token token))))))
Тут видно, как задаются возможные аргументы, какие у них могут быть флаги и короткие названия, как запускается парсинг аргументов и их значения присваиваются переменным внутри функции.
Абстракция defmain, написанная один раз, сама задаёт разумные дефолты и избавляет меня от написания или копирования всего этого бойлерплейта в каждой программе.
Такой вот DSL. Реальный.
В коде обращает внимание использование известного макро loop, которое является частью стандарта CL. Это действительно специализированная конструкция со своим особым синтаксисом. А вот макро defmain вполне заурядно. На обычном языке, который поддерживает замыкания, можно было бы сделать аналогичный комбинатор, который развернется в нечто подобное тому, что Вы привели.
А примером языка, поддерживающего конструирование DSL, является Rebol с его диалектами.
Скобки здесь при том, что это синтаксический рудимент Лиспа
Скобки — это не рудимент, это преимущество лиспа. Поначалу они действительно кажутся неудобными, но стоит лишь перестроиться и начать думать "сбалансированными выражениями" вместо символов и строк, работать с кодом становится гораздо удобнее, и современные плагины для работы с Lisp (paredit, lispy) сильно помогают. Они превращают редактор текста в редактор синтаксического дерева программы. Вот небольшое демо с демонстрацией базовых возможностей paredit.
После работы с Lisp начинаешь думать по-другому и в других языках: я в основном пишу на C++ в Emacs, и работа со "сбалансированными выражениями" прочно засела в моём workflow.
Чем DSL от функции отличается?
Правилами вычисления аргументов. У функции аргументы вычисляются, как правило, в определённом порядке. В в случае DSL могут не вычисляться вовсе, а как-то трансформироваться в код, например.
К примеру, loop макрос определяет язык для итерации по коллекциям и такой простой код:
DBAAS> (loop for i in '(1 2 3 4 5)
when (evenp i)
collect i)
Разворачивается в более сложную конструкцию, где некоторые аргументы макроса будут вычислены (1 2 3 4 5)
и (evenp i)
, а другие использованы для генерации кода реализующего бизнес-логику. В результате получится нечто вроде этого:
(block nil
(let ((i nil) (#:loop-list-827 '(1 2 3 4 5)))
(declare (type list #:loop-list-827))
(sb-loop::with-loop-list-collection-head (#:loop-list-head-828
#:loop-list-tail-829)
(tagbody
sb-loop::next-loop
(sb-loop::loop-desetq i (car #:loop-list-827))
(sb-loop::loop-desetq #:loop-list-827 (cdr #:loop-list-827))
(if (evenp i)
(sb-loop::loop-collect-rplacd
(#:loop-list-head-828 #:loop-list-tail-829) (list i)))
(when (endp #:loop-list-827) (go sb-loop::end-loop))
(go sb-loop::next-loop)
sb-loop::end-loop
(return-from nil
(sb-loop::loop-collect-answer #:loop-list-head-828))))))
Вообще dsl можно и на функциях построить. Язык же может быть сколь угодно странным. К примеру, ORM Django или SQLAlchemy предоставляют DSL позволяющий определять модели данных и делать запросы выполняя цепочки методов.
Если уж хочется Java+Scheme, то есть Сlojure.
С другой стороны, сейчас для встраивания в приложения существуют Tcl, Python, Lua и многие-многие другие. Поэтому, кстати говоря, я призываю к критической, объективной оценке того, где действительно целесообразно применять Лисп. Ниже привожу несколько возможных вариантов.
1. Универсальное текстовое представление вычислений и данных для обмена между программными модулями. Здесь фигурирует, вообще говоря, не совсем Лисп, а его существенный элемент под названием S-выражения. В каких-то случаях S-выражения могут быть предназначены и для редактирования пользователями. Например, можно реализовать синтаксис ассемблера для какой-то экзотической архитектуры в форме Лисп-подобной нотации. В большинстве случаев ассемблерный код будет порожден компилятором, но можно учесть и вариант, когда на Лисп-подобном ассемблере придется писать человеку.
2. Миниатюрный диалоговый язык для удаленного управления скромным по ресурсам микроконтроллером. Существуют Лисп-подобные реализации, которые выгоднее использовать в таких условиях, чем даже Lua.
3. Средство для метапрограммирования и программных трансформаций. Тот, достаточно редкий, случай, когда важнейшим свойством Лиспа программа=данные действительно необходимо воспользоваться. Например, если требуется какая-то совершенно экзотическая программная конструкция, поддержки которой еще нет ни у Google, ни у Microsoft. «Нам совершенно необходим встроенный движок для программирования в ограничениях!» Здесь, однако, нужно серьезно поразмыслить, а нужно ли все это городить именно в рамках Лиспа.
Как можно видеть, использование Common Lisp не слишком-то увязывается с описанными выше пунктами. К CL у работодателя могут возникнуть резонные вопросы на тему того, кто будет поддерживать систему после Вас, стоит ли в целом овчинка выделки. В этом смысле показательны примеры игровой компании Naughty Dog и Пола Грэма с Yahoo. Другое дело, что если просто _нравится_ писать на CL, да и условия позволяют — почему бы и нет :)
Lisp нужен для того, чтобы писать на нем конфиги к Emacs)
зачем писать на LISP, и что писать на LISP?
Common Lisp – очень неплохо спроектированный язык. Да, в стандарте есть несколько моментов, которые сейчас не особо актуальны, но в целом язык очень интересный. Честно говоря, я с трудом переношу языки с динамической типизацией (Python и JavaScript снятся мне в кошмарах), но на CL я бы с удовольствием попробовал написать проект.
Для CL есть качественные компиляторы, в языке есть возможность указать типы, если хочется (да, аналог gradual typing был давным давно). Код, который генерит, к примеру, SBCL, на удивление быстр (я сабмитил решения для computer language benchmark game, мой код для SBCL иногда работает ощутимо шустрее, чем мой код для Haskell, оба решения были в топе).
Slime + Emacs – это приятная и мощная среда разработки, в которую вложено много человеко-лет труда. Её действительно иногда нетривиально настроить (нужно разобраться в терминологии, ASDF, системы, вот это всё). К примеру, компиляция совершенно не мешает разработке, ведь можно инкрементально компилировать код по одной функции за раз. При этом можно попросить компилятор показать ассемблерный код любой функции и посмотреть, как добавление аннотаций типов влияет на код, производимый компилятором.
Ну и если в проекте нужна генерация кода, CL тут просто вне конкуренции.
Единственное, что я бы не стал писать на CL – мелкие утилиты командной строки. Модель исполнения с тяжёлым долго-живущим образом не особо к этому располагает.
К слову, у языка довольно много активных пользователей. Например, бэкэнд ITA (ныне Google Flights) в значительной степени написан на CL.
На каком языке писать приложения промышленного уровня (на Lisp или на другом аналогичном развитом языке программирования) по большому счету определяется лишь историческими причинами и конкретными обстоятельствами.
Если попадаешь в среду, где много пишут на <какой-то язык> и для него есть библиотеки, курсы обучения и сообщество, то в конечном счете на этом языке и будут писать.
Конкретные отдельные особенности любого языка здесь уже вторичны, имеет место быть полезность в целом.
На лиспе в наших краях много не пишут потомучто тут нет среды для его развития. Почему ее нет это другой интересный вопрос.
По каким-то конкретным примерам кода нельзя понять хорош язык или нет т.к. для любого языка всегда можно найти и примеры и контрпримеры.
Для собственного развития и понимания, конечно нужно изучать разные языки и подходы. В данном случае статья очень удобна, объясняет как попробовать CL.
Кстати, opensource хорош тем, что его можно исправлять. Пока завёл issue про https: github.com/quicklisp/quicklisp-client/issues/167
Про паранойю неверная логика. Всегда есть шанс что вас убьёт молния, но это же не повод не мыть руки перед едой. Пересылать программы по http не вредно до первого роутерного ботнета. Или открытого вайфая. А раз уж http стоит по умолчанию, то это очень очевидная уязвимость которую вирусописателям возможно даже выгодно эксплуатировать.
У Common LISP есть какие либо преимущества перед Clojure? Или хотя бы то чего не в Clojure, может кто рассказать?
В Clojure есть ООП, за счёт полной совместимости с Java. Хотя я не представляю зачем это может понадобиться кроме обратной совместимости.
Макросы чтения в Clojure тоже есть если я правильно разобрался что это означает — можно экранировать выражение используя ` и оно не вычислиться.
Скомпилированная программа на CL загружается мгновенно.
Это особенности платформы JVM, на Node.js ClojureScript так же мгновенно запускается.
Вы все не то пишете, в Кложе нет ООП, только мульти-методы и протоколы, которые близки к идее "пусть объект знает, как сделать это". Напротив, в CL реализована мощная система полноценного ООП, CLOS (commo Lisp object system). Про макросы тоже неверно.
UTILS> (set-dispatch-macro-character
#\# #\s
#'(lambda (stream sb1 sb2)
(declare (ignore sb1 sb2))
(let ((cmd (with-output-to-string (out)
(do ((prev (read-char stream) curr)
(curr (read-char stream)
(read-char stream)))
((and (char= curr #\#)
(char= prev #\#)))
(write-char prev out)))))
(uiop:run-program cmd :output :string))))
T
UTILS> #s ls ~/quicklisp/ ##
"asdf.lisp
client-info.sexp
dists
local-projects
quicklisp
retired
setup.lisp
tmp
"
В каких случаях вам НЕ надо писать на Common Lisp:
- Если подобная вашей задача уже решалась уже много раз и вам, грубо говоря, надо просто оттранслировать ТЗ в код
- Если вы не хотите испытывать сложностей с поиском разработчиков на ваш проект
- Если ваш проект сильно зависит от большого количества сторонних библиотек (соотношение «собственного мяса» в пользу batteries сильно в пользу второго)
- Если вы хотите создать OpenSource проект с обширным community
В каких случаях вам надо использовать Common Lisp:
- Если так уж получилось, хоть так получаться и не должно (но всё равно получается...), что вы единственный разработчик в проекте, и нет больших шансов изменить это в будущем — эффективность языка здесь вам очень поможет;
- Если вы вообще не представляете, с какого конца подойти к решению стоящей перед вами задачи — exploratory programming — очень сильная сторона CL;
- Если проект требует активного использования DSL — макросы очень помогут;
- Если вас не пугает нехватка библиотек;
- Если вы хотите сделать прототип, который потом можно было бы переписать на другом языке.
Понимаю, что это означает не слишком обширную применимость Common Lisp'а. Я, например, по работе сейчас в основном пишу на Go, и пишу довольно много. Но при всём при этом ни Go, ни Python, C# и C++ перед этим, в отличие от CL, так и не стали для меня языками, на которых мне было бы удобно думать.
Все проблемы с библиотеками решены в Clojure.
Scala, мне не понятно чем он так хорош, на мой взгляд только геморой. То что можно написать на Clojure быстро и хорошо на скала потребует кучу времени и плясок с типами. Это из опыта в продакшене а не домашних хелоуворлдов.
По CL, не могу говорить т.к. его не знаю могу опираться только на clojure, те особенности что вы перечислили я для себя не могу отнести к преимуществам. Интеграция с JVM для меня огромный плюс, думаю это обьективно, есть сомнения что доступность библиотек и базовых возможностей необходимых для современной разработки хоть немного близка — поддержка многопоточной среды, средства отладки и мониторинга. Хотя может вам это не нужно. Но я не представяю как возможны современные серверные приложения без этого.
Попробуйте «быстро и хорошо» написать на Clojure приложение для Android. На Scala я пишу клиентские приложения, которые без изменения кода работают на рабочем столе, в браузере и на мобильном устройстве. Серверные приложения спокойно пишу и на Scala и на СL и на Rust. У каждого из этих мультипарадигмальных языков есть свои прелести. Интеграция с JVM иногда плюс иногда минус, зависит от задач.
Как начать писать код на Lisp?