Pull to refresh

Comments 63

У меня другой вопрос: зачем писать на LISP, и что писать на LISP? Для чего этот язык хорош, кроме как развлечься и попытаться сломать процедурные парадигмы в своём мышлении?
На F# или Erlang тоже можно ломать процедурные парадигмы в своём мышлении, а за них ещё и платят деньги.
Потому хотелось бы аргументов почему лучше выбрать именно LISP, а не F# или Erlang?

PS функционально развлечься можно и на Logo и на unLambda.

Гомоиконичности в приведенных вами языках нет, как минимум:)

Чтобы написать лисп в лисп, чтобы можно было исполнять лисп, пока пишешь на лисп. Случайно написав плагин для Emacs
Вот тоже хотел задать этот вопрос. Есть ли реальные задачи, которые на этом языке быстрее и проще решаются или это просто академическая игрушка…
Обычные прикладные задачи, особенно, если заказчик не до конца представляет что хочет.
У меня на CL написано что-то типа 1С. И CL — это не функциональный а мультипарадигмальный язык программирования.
AutoCAD содержит LISP, но для его изучения лучше поставить сам AutoCAD.
UFO just landed and posted this here
на вопрос «что писать на лисп?» ответ простой — всё.
А вот «зачем?» — да, вопрос.

Хорошо язык тем, что сам язык — суть AST, всё остальное сделано на нём самом же (ну, почти). Когда начинаешь «дмать на лисп», сначала обретаешь ясное понимание, как всё вокруг работает, и далее, если интересно — никаких ограничений.

Ломать парадигмы в голове, на мой вкус лучше хаскелем (и потом на хаскеле тоже можно писать всё). По крайней мере, после сотен тысяч строк кода, что мне довелось читать за свою жизнь, очень хочется, что бы авторы были не ближе к машине, а ближе к сути прикладной задачи.
UFO just landed and posted this here
что такое «в нотации json»? Фигурные скобочки вместо круглых? Идентификаторы в кавычках?

Кажется, что написать интерпретатор лиспа в любой нотации — это такой проект-на-выходные. Не удивлюсь, если внезапный 'npm install js-notated-lisp' вытащит пакет и установит. Хотя это скорее троллейбус из буханки хлеба…
UFO just landed and posted this here
Кому-то нужен неигрушечный лисп в нотации json?

Мне кажется, кому сейчас нужен современный клевый лисп — все на closure перешли.
[] это список, {} это множество. Добавьте туда функции и будет, то что надо.

Почти так устроен, например, Wolfram Language. Квадратные скобки в нём — это список. Причем для того, чтобы не пугать людей, первый элемент списка пишется перед первой квадратной скобкой. И получается почти привычное f[x] вместо [f, x]. Люди пользуются и не подозревают, что пишут практически на Лиспе. :)


Фигурные скобки тоже есть, но это синтаксический сахар для списка, у которого первый элемент — слово List. Это что-то вроде пометки, что список содержит данные. Да и вообще, в Wolfram Language практически все — это сахар над списками.

UFO just landed and posted this here
> Причем для того, чтобы не пугать людей, первый элемент списка пишется перед первой квадратной скобкой. И получается почти привычное f[x] вместо [f, x]. Люди пользуются и не подозревают, что пишут практически на Лиспе. :)

Это старая тема S-expression vs M-expression. История говорит, что LISP начали создавать, имея планы ввести M-expressions для пользователей, но S-expressions внутри, но в итоге остановились на S-expressions везде (и, я бы сказал, правильно).

Сейчас M-expressions ещё родное средство для Kxʼовых k и q.
Пока программисты на других языках решают задачу в рамках установленных их языком правил, программисты на LISP пишут DSL, чьи правила подходят для решения задачи наилучшим образом. © Нил Форд

… в результате у программистов на других языках задача решена, а программисты на LISP только закончили делать DSL?

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

Наермиер, в рамках питона можно соорудить DSL поиск по «python dsl» вываливает кучу статей и даже видео. В той же группе, скажем и ruby.

Из этого направления выпадает java, и может еще какие-то…
К сожалению, частые разговоры о DSL в Лисп-кругах мало подкрепляется реальными делами. Да, DSL написать можно, но в реальности пишут нечто маловразумительное на уровне все тех же многочисленных скобок. Собственно, с языком Форт подобная ситуация. Декларируется метапрограммирование и создание DSL, но реальных практических примеров подобного очень немного. На мой взгляд, тому в Лиспе есть две причины: 1) слабая существующая поддержка построения внутренних DSL, 2) необходимость для обычного разработчика быть по совместительству еще и неплохим архитектором языков.

Конечно же Лисп имеет смысл применять там, где не хватает готовых решений из традиционных ЯП. Но лично меня удивляет такое внимание именно к неуклюжему 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. Реальный.

Скобки здесь при том, что это синтаксический рудимент Лиспа, который просочился в нотацию нашего DSL. Спасибо за пример. Заранее прошу не принимать мою критику в штыки. Заслуживает уважения уже то, что Вы выбрали Лисп в качестве инструмента разработки.

В коде обращает внимание использование известного макро loop, которое является частью стандарта CL. Это действительно специализированная конструкция со своим особым синтаксисом. А вот макро defmain вполне заурядно. На обычном языке, который поддерживает замыкания, можно было бы сделать аналогичный комбинатор, который развернется в нечто подобное тому, что Вы привели.

А примером языка, поддерживающего конструирование DSL, является Rebol с его диалектами.
При этом скобочность лиспа позволяет не писать поддержку каждого нового DSL в редакторе. Всё работает «из коробки». И манипуляции с кодом серьёзно упрощаются. Так что я бы не считал это рудиментом. Просто особенность к которой быстро привыкаешь.
По своему опыту скажу что хватает недели регулярного кодирования на лиспе чтобы привыкнуть к скобкам и воспринимать их естественно. При этом форматирование кода сильнее всего напоминает, как ни странно, Python.
Скобки здесь при том, что это синтаксический рудимент Лиспа

Скобки — это не рудимент, это преимущество лиспа. Поначалу они действительно кажутся неудобными, но стоит лишь перестроиться и начать думать "сбалансированными выражениями" вместо символов и строк, работать с кодом становится гораздо удобнее, и современные плагины для работы с 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 позволяющий определять модели данных и делать запросы выполняя цепочки методов.

Lisp это и так уже язык программирования.

Проект в императивном стиле, интерпретатор в функциональном. Зачем такое жесткое разграничения парадигм. К тому же СL и как императивному языку есть что сказать.
Если уж хочется Java+Scheme, то есть Сlojure.
На мой взгляд, разграничение вполне логичное и оно проходит не в области парадигм, а в области архитектуры приложения. Нижнему уровню в большинстве случаев и полагается быть императивным. Собственно, примеров, когда интерпретатор Лиспа встроен в большую программную систему для расширения возможностей последней — масса. Даже в старых компьютерных играх такое вполне практиковалось, можно вспомнить древний Zork, где использовался далект Лиспа под названием MDL или более позднюю Abuse. Я уже не говорю про Autocad, Cakewalk и проч.

С другой стороны, сейчас для встраивания в приложения существуют Tcl, Python, Lua и многие-многие другие. Поэтому, кстати говоря, я призываю к критической, объективной оценке того, где действительно целесообразно применять Лисп. Ниже привожу несколько возможных вариантов.

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

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

3. Средство для метапрограммирования и программных трансформаций. Тот, достаточно редкий, случай, когда важнейшим свойством Лиспа программа=данные действительно необходимо воспользоваться. Например, если требуется какая-то совершенно экзотическая программная конструкция, поддержки которой еще нет ни у Google, ни у Microsoft. «Нам совершенно необходим встроенный движок для программирования в ограничениях!» Здесь, однако, нужно серьезно поразмыслить, а нужно ли все это городить именно в рамках Лиспа.

Как можно видеть, использование Common Lisp не слишком-то увязывается с описанными выше пунктами. К CL у работодателя могут возникнуть резонные вопросы на тему того, кто будет поддерживать систему после Вас, стоит ли в целом овчинка выделки. В этом смысле показательны примеры игровой компании Naughty Dog и Пола Грэма с Yahoo. Другое дело, что если просто _нравится_ писать на CL, да и условия позволяют — почему бы и нет :)
Во всех моих проектах в качестве нижнего уровня использовался мультипарадигмальный язык. А в качестве встроенного интерпретатора язык на который знают или легко способны выучить большинство программистов. Получилось так Rust+Lua, Scala+(Lua и JavaScript). В случаи с СL он и нижний и верхний уровень.

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.

UFO just landed and posted this here
UFO just landed and posted this here
Когда-то, на заре развития сайта StackOverflov, я задавал там подобный вопрос. Несмотря на то, что я сам не очень понимал, что именно спрашиваю, топовый ответ там, имхо, очень крут.
Да, ответ отличный. Спасибо за ссылку!
Хотя бы, потому что это Лисп!
Заголовок спойлера
image

На каком языке писать приложения промышленного уровня (на Lisp или на другом аналогичном развитом языке программирования) по большому счету определяется лишь историческими причинами и конкретными обстоятельствами.


Если попадаешь в среду, где много пишут на <какой-то язык> и для него есть библиотеки, курсы обучения и сообщество, то в конечном счете на этом языке и будут писать.


Конкретные отдельные особенности любого языка здесь уже вторичны, имеет место быть полезность в целом.


На лиспе в наших краях много не пишут потомучто тут нет среды для его развития. Почему ее нет это другой интересный вопрос.


По каким-то конкретным примерам кода нельзя понять хорош язык или нет т.к. для любого языка всегда можно найти и примеры и контрпримеры.


Для собственного развития и понимания, конечно нужно изучать разные языки и подходы. В данном случае статья очень удобна, объясняет как попробовать CL.

Спасибо за коммент.

На лиспе в наших краях много не пишут потомучто тут нет среды для его развития.

Среда не появится, если её не создать. Этим надо заниматься – писать тулинг, улучшать библиотеки, создавать компании, которые будут использовать эту экосистему. Я понемногу этим занимаюсь.
Менеджеры пакетов работают по https или подписывают библиотеки? Пробовал изучить липс. Что бы запустить сервер, требовалось скачать либу. Насторожило что все качалось по http
Проверил. Да, по HTTP. Но ведь если параноить, то надо параноить по полной – вычитывать весь код всех своих opensource зависимостей и их зависимостей, и зависимостей их зависимостей.

Кстати, opensource хорош тем, что его можно исправлять. Пока завёл issue про https: github.com/quicklisp/quicklisp-client/issues/167

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

Я замечал подмены пакетов дебиан, но они не ставились из-за неверной подписи

У Common LISP есть какие либо преимущества перед Clojure? Или хотя бы то чего не в Clojure, может кто рассказать?

Это очень холиварный вопрос :) Первое, что приходит мне в голову — SBCL может компилировать программы в исполняемые файлы и у него есть настоящая оптимизация хвостовой рекурсии.
CL — мультипарадигмальный язык, CLojure — функциональный. В СL есть CLOS система объектно-ориентированного программирования. В CL есть макросы чтения. Скомпилированная программа на CL загружается мгновенно. в отличии от программы на Clojure.

В Clojure есть ООП, за счёт полной совместимости с Java. Хотя я не представляю зачем это может понадобиться кроме обратной совместимости.
Макросы чтения в Clojure тоже есть если я правильно разобрался что это означает — можно экранировать выражение используя ` и оно не вычислиться.


Скомпилированная программа на CL загружается мгновенно.

Это особенности платформы JVM, на Node.js ClojureScript так же мгновенно запускается.

Нет, в Clojure нет полноценного ООП. Нет, Вы не разобрались с макросами чтения. Нет, это не только оcособенности платформы JVM но и особенности реализации самого Clojure. Напишите helloworld на Java и на Clojure и увидите разницу при загрузке.

Вы все не то пишете, в Кложе нет ООП, только мульти-методы и протоколы, которые близки к идее "пусть объект знает, как сделать это". Напротив, в CL реализована мощная система полноценного ООП, CLOS (commo Lisp object system). Про макросы тоже неверно.

Java-style OOP с CLOS сравнивать как-то даже не смешно. Не, понятно, на ООП в принципе свет клином не сошёлся, но в некоторых случаях мощная система ООП очень даже не мешает.
макросы чтения это чуть больше, чем квазицитирование, вот более интересный пример, чем обратная кавычка:
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 — как человека, который использовал его в продакшне, например, для управления промышленными ускорителями электронов.

В каких случаях вам НЕ надо писать на Common Lisp:
  1. Если подобная вашей задача уже решалась уже много раз и вам, грубо говоря, надо просто оттранслировать ТЗ в код
  2. Если вы не хотите испытывать сложностей с поиском разработчиков на ваш проект
  3. Если ваш проект сильно зависит от большого количества сторонних библиотек (соотношение «собственного мяса» в пользу batteries сильно в пользу второго)
  4. Если вы хотите создать OpenSource проект с обширным community

В каких случаях вам надо использовать Common Lisp:
  1. Если так уж получилось, хоть так получаться и не должно (но всё равно получается...), что вы единственный разработчик в проекте, и нет больших шансов изменить это в будущем — эффективность языка здесь вам очень поможет;
  2. Если вы вообще не представляете, с какого конца подойти к решению стоящей перед вами задачи — exploratory programming — очень сильная сторона CL;
  3. Если проект требует активного использования DSL — макросы очень помогут;
  4. Если вас не пугает нехватка библиотек;
  5. Если вы хотите сделать прототип, который потом можно было бы переписать на другом языке.

Понимаю, что это означает не слишком обширную применимость Common Lisp'а. Я, например, по работе сейчас в основном пишу на Go, и пишу довольно много. Но при всём при этом ни Go, ни Python, C# и C++ перед этим, в отличие от CL, так и не стали для меня языками, на которых мне было бы удобно думать.

Все проблемы с библиотеками решены в Clojure.

За что приходится платить, например, отсутствием CLOS / MOP, зависимостью от JVM и тд. К тому же в принципе без дополнительных обёрток использование нелисповских библиотек в лиспах обычно приводит к довольно… хм… своеобразному коду. Мало иметь возможность использовать библиотеку, надо, чтобы она сочеталась с конструкциями языка. Так-то в CL можно тоже библиотеки для C легко использовать, а в Сlasp, например, и C++. В принципе, срач разводить на эту тему не хочется, но у меня как-то Clojure не зашёл. Хотя, наверное, если JVM для вас комфортен, Clojure в каких-то случаях может быть и лучше.
Я лет 5 использовал Clojure в продакшен. Но сейчас использую Scala или CL и по возможности, перевожу написанное на эти языки.

Scala, мне не понятно чем он так хорош, на мой взгляд только геморой. То что можно написать на Clojure быстро и хорошо на скала потребует кучу времени и плясок с типами. Это из опыта в продакшене а не домашних хелоуворлдов.
По CL, не могу говорить т.к. его не знаю могу опираться только на clojure, те особенности что вы перечислили я для себя не могу отнести к преимуществам. Интеграция с JVM для меня огромный плюс, думаю это обьективно, есть сомнения что доступность библиотек и базовых возможностей необходимых для современной разработки хоть немного близка — поддержка многопоточной среды, средства отладки и мониторинга. Хотя может вам это не нужно. Но я не представяю как возможны современные серверные приложения без этого.

Вступать в религиозную дискуссию динамическая типизация против статической я не буду.
Попробуйте «быстро и хорошо» написать на Clojure приложение для Android. На Scala я пишу клиентские приложения, которые без изменения кода работают на рабочем столе, в браузере и на мобильном устройстве. Серверные приложения спокойно пишу и на Scala и на СL и на Rust. У каждого из этих мультипарадигмальных языков есть свои прелести. Интеграция с JVM иногда плюс иногда минус, зависит от задач.
Sign up to leave a comment.

Articles