Кто-то пытается продать Lisp мне, как супер мощный язык, который может делать все когда-либо, а затем некоторые.
Есть ли практический пример кода Lisp power?
(желательно рядом с эквивалентной логикой, закодированной на обычном языке.)
Кто-то пытается продать Lisp мне, как супер мощный язык, который может делать все когда-либо, а затем некоторые.
Есть ли практический пример кода Lisp power?
(желательно рядом с эквивалентной логикой, закодированной на обычном языке.)
Мне нравятся макросы.
Здесь код для заполнения атрибутов для людей из LDAP. У меня просто оказалось, что этот код лежит и фиксируется, что он будет полезен другим.
Некоторые люди путаются над предполагаемым временем выполнения макросов, поэтому я добавил попытку прояснить ситуацию в конце.
(defun ldap-users ()
(let ((people (make-hash-table :test 'equal)))
(ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))"))
(let ((mail (car (ldap:attr-value ent 'mail)))
(uid (car (ldap:attr-value ent 'uid)))
(name (car (ldap:attr-value ent 'cn)))
(phonenumber (car (ldap:attr-value ent 'telephonenumber))))
(setf (gethash uid people)
(list mail name phonenumber))))
people))
Вы можете думать о "let binding" как локальной переменной, которая исчезает вне формы LET. Обратите внимание на форму привязок - они очень похожи, отличаются только атрибутом объекта LDAP и именем ( "локальная переменная" ) для привязки значения. Полезно, но немного многословно и содержит дублирование.
Теперь, было бы неплохо, если бы у нас не было всего этого дублирования? Общая идиома - это WITH -... макросы, которые связывают значения, основанные на выражении, с которого вы можете получить значения. Давайте представим наш собственный макрос, который работает именно так, WITH-LDAP-ATTRS и заменит его в нашем исходном коде.
(defun ldap-users ()
(let ((people (make-hash-table :test 'equal))) ; equal so strings compare equal!
(ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))"))
(with-ldap-attrs (mail uid name phonenumber) ent
(setf (gethash uid people)
(list mail name phonenumber))))
people))
Вы видели, как группа строк внезапно исчезла и была заменена только одной строкой? Как это сделать? Использование макросов, конечно, - код, который пишет код! Макросы в Lisp - совершенно другое животное, чем те, которые вы можете найти в C/С++ с помощью препроцессора: здесь вы можете запустить реальный Lisp код (а не #define
пух в cpp), который генерирует код Lisp, прежде чем компилируется другой код. Макросы могут использовать любой реальный Lisp код, т.е. Обычные функции. По существу никаких ограничений.
Итак, посмотрим, как это было сделано. Чтобы заменить один атрибут, мы определяем функцию.
(defun ldap-attr (entity attr)
`(,attr (car (ldap:attr-value ,entity ',attr))))
Синтаксис backquote выглядит немного волосатым, но то, что он делает, легко. Когда вы вызываете LDAP-ATTRS, он выплевывает список, содержащий значение attr
(это запятая), а затем car
( "первый элемент в списке" (на самом деле, пары "пары" ), и там на самом деле является функцией, называемой first
, которую вы также можете использовать), которая получает первое значение в списке, возвращаемом ldap:attr-value
. Поскольку это не код, который мы хотим запустить при компиляции кода (получение значений атрибутов - это то, что мы хотим сделать, когда мы запускаем программу), мы не добавляем запятую перед вызовом.
В любом случае. Двигаемся вместе с остальной частью макроса.
(defmacro with-ldap-attrs (attrs ent &rest body)
`(let ,(loop for attr in attrs
collecting `,(ldap-attr ent attr))
,@body))
,@
-syntax заключается в том, чтобы помещать содержимое списка где-то вместо фактического списка.
Вы можете легко убедиться, что это даст вам правильную вещь. Макросы часто пишутся следующим образом: вы начинаете с кода, который хотите упростить (вывод), что вы хотите написать вместо этого (ввод), а затем вы начинаете форматирование макроса до тех пор, пока ваш ввод не даст правильный результат. Функция macroexpand-1
сообщит вам, соответствует ли ваш макрос:
(macroexpand-1 '(with-ldap-attrs (mail phonenumber) ent
(format t "~a with ~a" mail phonenumber)))
оценивается как
(let ((mail (car (trivial-ldap:attr-value ent 'mail)))
(phonenumber (car (trivial-ldap:attr-value ent 'phonenumber))))
(format t "~a with ~a" mail phonenumber))
Если вы сравниваете LET-привязки расширенного макроса с кодом в начале, вы обнаружите, что он в той же форме!
Макрос - это код, который запускается во время компиляции, с добавленным твистом, который может вызвать любую обычную функцию или макрос, как им заблагорассудится! Это не намного больше, чем причудливый фильтр, принимая некоторые аргументы, применяя некоторые преобразования, а затем подавая компилятору результирующие s-exps.
В принципе, он позволяет вам писать свой код в глаголах, которые можно найти в проблемной области, вместо примитивов низкого уровня с языка! В качестве глупого примера рассмотрим следующее (если when
еще не был встроен)::
(defmacro my-when (test &rest body)
`(if ,test
(progn ,@body)))
if
- это встроенный примитив, который позволит вам только выполнить одну форму в ветвях, и если вы хотите иметь более одного, ну, вам нужно использовать progn
::
;; one form
(if (numberp 1)
(print "yay, a number"))
;; two forms
(if (numberp 1)
(progn
(assert-world-is-sane t)
(print "phew!"))))
С нашим новым другом my-when
мы могли бы как a) использовать более подходящий глагол, если у нас нет ложной ветки, и b) добавить неявный оператор последовательности, т.е. progn
::
(my-when (numberp 1)
(assert-world-is-sane t)
(print "phew!"))
Скомпилированный код никогда не будет содержать my-when
, хотя, поскольку в первом проходе все макросы расширяются, поэтому не требуется никакого исполнения во время выполнения!
Lisp> (macroexpand-1 '(my-when (numberp 1)
(print "yay!")))
(if (numberp 1)
(progn (print "yay!")))
Обратите внимание, что macroexpand-1
выполняет только один уровень разложений; это возможно (скорее всего, на самом деле!), что расширение продолжается еще дальше. Однако в конечном итоге вы столкнетесь с деталями реализации компилятора, которые часто не очень интересны. Но дальнейшее расширение результата в конечном итоге либо даст вам более подробную информацию, либо просто ваш вход s-exp обратно.
Надеюсь, что это прояснит ситуацию. Макросы - мощный инструмент, и одна из функций в Lisp мне нравится.
Лучшим примером, о котором я могу думать, является широко доступная книга Пауля Грэма, В Lisp. Полный PDF файл можно скачать по ссылке, которую я только что дал. Вы также можете попробовать Практический общий Lisp (также полностью доступный в Интернете).
У меня много непрактичных примеров. Однажды я написал программу примерно в 40 строках lisp, которые могли бы анализировать себя, обрабатывать свой источник как список lisp, выполнять обход дерева по списку и строить выражение, которое оценивалось в WALDO, если идентификатор waldo существовал в источник или оценить нуль, если waldo нет. Возвращаемое выражение было построено путем добавления вызовов к машине /cdr в исходный исходный код, который был разобран. Я не знаю, как это сделать на других языках в 40 строках кода. Возможно, perl может сделать это еще меньше строк.
Вы можете найти эту статью полезной: http://www.defmacro.org/ramblings/lisp.html
Тем не менее, очень, очень трудно дать короткие практические примеры мощности Lisp, потому что он действительно сияет только в нетривиальном коде. Когда ваш проект вырастет до определенного размера, вы оцените функции абстракции Lisp и будьте рады, что вы их используете. Разумно короткие образцы кода, с другой стороны, никогда не дадут вам удовлетворительной демонстрации того, что делает Lisp отличным, потому что предопределенные аббревиатуры других языков будут выглядеть более привлекательными в небольших примерах, чем Lisp гибкость при управлении абстракциями, специфичными для домена.
На самом деле, хорошим практическим примером является макрос LOOP Lisp.
http://www.ai.sri.com/pkarp/loop.html
Макрос LOOP - это просто макрос Lisp. Тем не менее он в основном определяет мини-цикл DSL (Domain Specific Language).
Когда вы просматриваете этот небольшой учебник, вы можете видеть (даже как новичок), что трудно понять, какая часть кода является частью макроса Loop, а какая "нормальная" Lisp.
И это один из ключевых компонентов выразительности Лисса, что новый код действительно нельзя отличить от системы.
В то время как, скажем, в Java, вы не можете (с первого взгляда) узнать, какая часть программы поступает из стандартной библиотеки Java по сравнению с вашим собственным кодом или даже сторонней библиотекой, вы знаете, какая часть кода - это язык Java, а не просто вызовы методов для классов. Конечно, это ВСЕ "язык Java", но как программист, вы ограничены только выражением своего приложения в виде комбинации классов и методов (а теперь и аннотаций). В то время как в Lisp, буквально все для захватов.
Рассмотрим Common SQL-интерфейс для подключения Common Lisp к SQL. Здесь http://clsql.b9.com/manual/loop-tuples.html, они показывают, как макрос CL Loop расширяется, чтобы заставить SQL привязать "гражданина первого класса".
Вы также можете наблюдать конструкции, такие как "[выберите [имя-фамилия] [фамилия]: из [employee]: order-by [last-name]]". Это часть пакета CL-SQL и реализована как "макрос читателя".
См. в Lisp, вы можете не только создавать макросы для создания новых конструкций, таких как структуры данных, структуры управления и т.д. Но вы даже можете изменить синтаксис языка с помощью макроса читателя. Здесь они используют макрос читателя (в случае, "символ" ), чтобы перейти к режиму SQL, чтобы заставить SQL работать как встроенный SQL, а не как простые строки, как на многих других языках.
Как разработчики приложений, наша задача - преобразовать наши процессы и конструкции в форму, которую может понять процессор. Это означает, что мы, неизбежно, должны "поговорить" с компьютерным языком, так как он "не понимает" нас.
Common Lisp - одна из немногих сред, в которой мы можем не только создавать наше приложение сверху вниз, но и где мы можем поднять язык и среду, чтобы встретить нас на полпути. Мы можем кодировать с обоих концов.
Разум, такой элегантный, как это может быть, это не панацея. Очевидно, что существуют другие факторы, которые влияют на выбор языка и окружающей среды. Но это, безусловно, стоит учиться и играть. Я думаю, что обучение Lisp - отличный способ продвинуть ваше программирование даже на других языках.
Мне нравится Общая Lisp Система объектов (CLOS) и многоточие.
Большинство, если не все, объектно-ориентированных языков программирования имеют основные понятия классов и методов. Следующий фрагмент в Python определяет классы PeelingTool и Vegetable (что-то похожее на шаблон посетителя):
class PeelingTool:
"""I'm used to peel things. Mostly fruit, but anything peelable goes."""
def peel(self, veggie):
veggie.get_peeled(self)
class Veggie:
"""I'm a defenseless Veggie. I obey the get_peeled protocol
used by the PeelingTool"""
def get_peeled(self, tool):
pass
class FingerTool(PeelingTool):
...
class KnifeTool(PeelingTool):
...
class Banana(Veggie):
def get_peeled(self, tool):
if type(tool) == FingerTool:
self.hold_and_peel(tool)
elif type(tool) == KnifeTool:
self.cut_in_half(tool)
Вы помещаете метод peel
в PeelingTool и принимаете Banana. Но он должен принадлежать классу PeelingTool, поэтому его можно использовать, только если у вас есть экземпляр класса PeelingTool.
Общая версия Lisp Object System:
(defclass peeling-tool () ())
(defclass knife-tool (peeling-tool) ())
(defclass finger-tool (peeling-tool) ())
(defclass veggie () ())
(defclass banana (veggie) ())
(defgeneric peel (veggie tool)
(:documentation "I peel veggies, or actually anything that wants to be peeled"))
;; It might be possible to peel any object using any tool,
;; but I have no idea how. Left as an exercise for the reader
(defmethod peel (veggie tool)
...)
;; Bananas are easy to peel with our fingers!
(defmethod peel ((veggie banana) (tool finger-tool))
(with-hands (left-hand right-hand) *me*
(hold-object left-hand banana)
(peel-with-fingers right-hand tool banana)))
;; Slightly different using a knife
(defmethod peel ((veggie banana) (tool knife-tool))
(with-hands (left-hand right-hand) *me*
(hold-object left-hand banana)
(cut-in-half tool banana)))
Все, что угодно, может быть записано на любом языке, который завершает Тьюринг; разница между языками заключается в том, сколько обручей вам нужно перепрыгнуть, чтобы получить эквивалентный результат.
Мощные языки, такие как Общие Lisp, с такими функциями, как макросы и CLOS, позволяют быстро и легко достичь результатов без прыгая через так много обручей, что вы либо соглашаетесь на решение подпапки, либо оказываетесь в кенгуру.
В Lisp есть много возможностей для убийцы, но макросы - это то, что я люблю, особенно потому, что между тем, что определяет язык, и тем, что я определяю, больше не существует барьера. Например, Common Lisp не имеет конструкцию while. Я когда-то реализовал это в своей голове, гуляя. Он прост и чист:
(defmacro while (condition &body body)
`(if ,condition
(progn
,@body
(do nil ((not ,condition))
,@body))))
Et voilà! Вы просто расширили общий язык Lisp новой фундаментальной конструкцией. Теперь вы можете:
(let ((foo 5))
(while (not (zerop (decf foo)))
(format t "still not zero: ~a~%" foo)))
Что бы напечатать:
still not zero: 4
still not zero: 3
still not zero: 2
still not zero: 1
Выполнение этого на любом языке без Lisp остается в качестве упражнения для читателя...
Я нашел эту статью довольно интересной:
Сравнение языков программирования: Lisp vs C++
Автор статьи, Брэндон Корфман, пишет об исследовании, которое сравнивает решения в Java, C++ и Lisp с проблемой программирования, а затем записывает собственное решение в C++. Контрольным решением является Питер Норвиг, 45 строк Lisp (написано через 2 часа).
Корфман считает, что его решение трудно сократить до менее 142 строк C++/STL. Его анализ того, почему, интересно читать.
То, что мне больше всего нравится в системах Lisp (и Smalltalk), заключается в том, что они чувствуют себя живыми. Вы можете легко исследовать и модифицировать системы Lisp во время работы.
Если это звучит загадочно, запустите Emacs и введите код Lisp. Введите C-M-x
и voilà! Вы только что изменили Emacs из Emacs. Вы можете продолжать и переопределять все функции Emacs во время работы.
Другое дело, что эквивалентность code = list делает границу между кодом и данными очень тонкими. А благодаря макросам очень легко расширить язык и быстро сделать DSL.
Например, можно закодировать базовый HTML-конструктор, с которым код очень близок к выпуску HTML-вывода:
(html
(head
(title "The Title"))
(body
(h1 "The Headline" :class "headline")
(p "Some text here" :id "content")))
= >
<html>
<head>
<title>The title</title>
</head>
<body>
<h1 class="headline">The Headline</h1>
<p id="contents">Some text here</p>
</body>
</html>
В коде Lisp автоотчет делает код похожим на результат, за исключением того, что закрывающие теги отсутствуют.
Посмотрите, как вы можете расширить Common Lisp с помощью шаблона XML: cl-quasi-quote XML-пример, страница проекта,
(babel:octets-to-string
(with-output-to-sequence (*html-stream*)
<div (constantAttribute 42
someJavaScript `js-inline(print (+ 40 2))
runtimeAttribute ,(concatenate 'string "&foo" "&bar"))
<someRandomElement
<someOther>>>))
=>
"<div constantAttribute=\"42\"
someJavaScript=\"javascript: print((40 + 2))\"
runtimeAttribute=\"&foo&bar\">
<someRandomElement>
<someOther/>
</someRandomElement>
</div>"
Это в основном то же самое, что и Lisp backtick reader (который используется для квазициклирования списка), но он также работает для различных других вещей, таких как XML (установленный в специальном синтаксисе < > ), JavaScript (установлен на ` js-inline) и т.д.
Чтобы было ясно, что это реализовано в пользовательской библиотеке! И он компилирует статические части XML, JavaScript и т.д. В UTF-8 закодированные массивы литералов, которые готовы к записи в сетевой поток, С помощью простой ,
(запятая) вы можете вернуться к Lisp и чередовать полученные данные в массивах литералов.
Это не для слабонервных, но это то, что библиотека компилирует выше:
(progn
(write-sequence
#(60 100 105 118 32 99 111 110 115 116 97 110 116 65 116 116 114 105 98
117 116 101 61 34 52 50 34 32 115 111 109 101 74 97 118 97 83 99 114
105 112 116 61 34 106 97 118 97 115 99 114 105 112 116 58 32 112 114
105 110 116 40 40 52 48 32 43 32 50 41 41 34 32 114 117 110 116 105
109 101 65 116 116 114 105 98 117 116 101 61 34)
*html-stream*)
(write-quasi-quoted-binary
(let ((*transformation*
#<quasi-quoted-string-to-quasi-quoted-binary {1006321441}>))
(transform-quasi-quoted-string-to-quasi-quoted-binary
(let ((*transformation*
#<quasi-quoted-xml-to-quasi-quoted-string {1006326E51}>))
(locally
(declare (sb-ext:muffle-conditions sb-ext:compiler-note))
(let ((it (concatenate 'string "runtime calculated: " "&foo" "&bar")))
(if it
(transform-quasi-quoted-xml-to-quasi-quoted-string/attribute-value it)
nil))))))
*html-stream*)
(write-sequence
#(34 62 10 32 32 60 115 111 109 101 82 97 110 100 111 109 69 108 101 109
101 110 116 62 10 32 32 32 32 60 115 111 109 101 79 116 104 101 114 47
62 10 32 32 60 47 115 111 109 101 82 97 110 100 111 109 69 108 101 109
101 110 116 62 10 60 47 100 105 118 62 10)
*html-stream*)
+void+)
Для справки, два больших байтовых вектора в приведенном выше примере выглядят так, когда они преобразуются в строку:
"<div constantAttribute=\"42\"
someJavaScript=\"javascript: print((40 + 2))\"
runtimeAttribute=\""
И второй:
"\">
<someRandomElement>
<someOther/>
</someRandomElement>
</div>"
И он хорошо сочетается с другими структурами Lisp, такими как макросы и функции. теперь сравните это с JSP...
Я был студентом ИИ в Массачусетском технологическом институте в 1970-х годах. Как и каждый другой студент, я думал, что язык имеет первостепенное значение. Тем не менее, Lisp был основным языком. Это некоторые вещи, которые я до сих пор считаю хорошим:
Символьная математика. Легко и поучительно писать символическое дифференцирование выражения и алгебраическое упрощение. Я все еще делаю это, хотя я делаю их в C-безотносительно.
Теорема доказана. Время от времени я продолжаю временную выпивку AI, например, пытаясь доказать правильность сортировки вставки. Для этого мне нужно сделать символическую манипуляцию, и я обычно возвращаюсь к Lisp.
Маленькие доменные языки. Я знаю, что Lisp на самом деле не практичен, но если я хочу попробовать немного DSL, не заставляя все обернуться в синтаксическом разборе и т.д., макросы Lisp упрощают работу.
Маленькие игровые алгоритмы, такие как поиск минимаксного дерева игр, могут выполняться как три строки.
В основном, то, что Lisp делает для меня, - это умственные упражнения. Тогда я могу перенести это на более практические языки.
P.S. Говоря об исчислении лямбда, то, что также началось в 1970-х годах, в том же искусстве ИИ, заключалось в том, что ОО начало вторгаться в каждый мозг, и каким-то образом интерес к тому, что он, похоже, вызвал большой интерес к тому, для чего он хорош. То есть работа над машинным обучением, естественный язык, видение, решение проблем, все в порядке, в то время как классы, сообщения, типы, полиморфизм и т.д. выходили на фронт.
Мне нравится этот пример макроса из http://common-lisp.net/cgi-bin/viewcvs.cgi/cl-selenium/?root=cl-selenium Это общая привязка Lisp к Selenium (среда тестирования веб-браузера), но вместо этого для сопоставления каждого метода, он считывает XML-документ Selenium собственного API в момент компиляции и генерирует код сопоставления с использованием макросов. Вы можете увидеть сгенерированный API здесь: common- lisp.net/project/cl-selenium/api/selenium-package/index.html
Это, по сути, управляет макросами с внешними данными, которые в данном случае являются XML-документом, но может быть столь же сложным, как чтение из базы данных или сети. Это возможность иметь всю среду Lisp, доступную вам во время компиляции.
Мне нравится только то, что я могу обновить код "run-time" без потери состояния приложения. Это полезно только в некоторых случаях, но когда это полезно, наличие его уже (или, только за минимальные затраты в процессе разработки) намного дешевле, чем реализовать его с нуля. Тем более, что это происходит при стоимости "нет почти".
Вы взглянули на это объяснение того, почему макросы являются мощными и гибкими? Никаких примеров на других языках, хотя, извините, но он может продать вас на макросах.
@Mark,
Хотя есть правда, что вы говорите, я считаю, что это не всегда так прямо.
Программисты и люди в целом не всегда тратят время, чтобы оценить все возможности и решить переключить языки. Часто это руководители, которые решают, или школы, которые учат первыми языками... и у программистов никогда не было необходимости вкладывать достаточное количество времени, чтобы добраться до определенного уровня, они могли решить, что этот язык экономит мне больше времени, чем этот язык.
Кроме того, вы должны признать, что языки, поддерживающие огромные коммерческие объекты, такие как Microsoft или Sun, всегда будут иметь преимущество на рынке по сравнению с языками без такой поддержки.
Чтобы ответить на исходный вопрос, Пол Грэм пытается привести пример здесь, хотя я признаю, что это не обязательно так же практично, как Я бы хотел: -)
Одна особенность, которая произвела на меня впечатление, - это способность писать собственное объектно-ориентированное программирование, если вам не нравится включить CLOS.
Один из них находится в Garnet, а другой в Paul Graham В Lisp.
Там также пакет под названием Screamer, который позволяет недетерминированное программирование (которое я не оценил).
Любой язык, который позволяет вам изменить его для поддержки различных парадигм программирования, должен быть гибким.
Вы можете найти этот пост от Эрика Норманда. Он описывает, как с ростом кодовой базы помогает Lisp, позволяя вам создавать язык до вашего приложения. Хотя это часто требует дополнительных усилий на раннем этапе, это дает вам большое преимущество позже.
Простой факт, что язык мультипарадигмы делает его очень гибким.
Дизайнеры языка любят спорить о том, почему этот язык или тот язык должен быть лучше или хуже априори, но ни один из этих аргументов действительно Материя много. В конечном итоге все языковые проблемы решаются при голосовании пользователей с их ногами.
Если [язык] делает людей более продуктивными, они будут использовать Это; когда приходит какой-то другой язык, что лучше (или если это здесь уже), тогда люди перейдут на этот язык. Это Закон, и это хорошо. Закон говорит мне, что Scheme (или любой другой Lispдиалект), вероятно, не является "правильным" языком: слишком много людей проголосовали за свои ноги за последние 30 лет.