Lisp的灵活性的实例?
有人试图把Lisp出售给我,作为一种超级强大的语言,可以做任何事情,然后一些。
有一个Lisp的力量的实际代码示例?
(最好与用常规语言编码的等效逻辑一起)。
我喜欢macros。
这是用于从LDAP中删除人员的属性的代码。 我只是碰巧把这些代码放在其他地方,并将其用于其他人。
有些人对macros的运行时惩罚感到困惑,所以我在最后澄清了一些事情。
在开始,有复制
(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”作为一个局部variables,在LET表单之外消失。 请注意绑定的forms – 它们非常相似,仅在LDAP实体的属性和用于绑定值的名称(“本地variables”)方面有所不同。 有用,但有点冗长,包含重复。
寻找美
现在,如果我们不必拥有所有的重复,这不是很好吗? 一个常见的习惯用法是WITH -…macros,它根据expression式来绑定值,你可以从中获取值。 我们来介绍一下我们自己的macros,就像WITH-LDAP-ATTRS那样工作,并把它replace成我们原来的代码。
(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))
你有没有看到一堆线条突然消失了,只换成了一条线? 这个怎么做? 使用macros,当然 – 编写代码的代码! Lisp中的macros与您在C / C ++中使用预处理器所能find的完全不同:在这里,您可以运行生成Lisp代码的真正的 Lisp代码(而不是cpp中的#define
fluff)其他代码被编译。 macros可以使用任何真正的Lisp代码,即普通函数。 基本上没有限制。
摆脱丑陋
那么,让我们看看这是如何完成的。 要replace一个属性,我们定义一个函数。
(defun ldap-attr (entity attr) `(,attr (car (ldap:attr-value ,entity ',attr))))
反引用的语法看起来有点毛,但它做的很容易。 当你调用LDAP-ATTRS时,它会吐出一个包含attr
(即逗号) 值的列表,然后是car
(“列表中的第一个元素”(实际上是cons对)),事实上函数first
被调用,您也可以使用),它接收由ldap:attr-value
返回的列表中的第一个ldap:attr-value
。 因为这不是我们编译代码时运行的代码(在运行程序时获取属性值就是我们想要做的),所以我们不会在调用之前添加逗号。
无论如何。 移动到macros的其余部分。
(defmacro with-ldap-attrs (attrs ent &rest body) `(let ,(loop for attr in attrs collecting `,(ldap-attr ent attr)) ,@body))
,@
语法是把列表的内容放在某个地方,而不是实际的列表。
结果
你可以很容易地validation这会给你正确的事情。 macros通常是这样写的:首先用你想做的简单的代码(输出),你想要写的代码(input),然后你开始build模macros,直到你的input给出正确的输出。 函数macroexpand-1
会告诉你,如果你的macros是正确的:
(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))
如果你比较扩展macros的LET绑定和开始的代码,你会发现它的forms是一样的!
编译时与运行时:macros与函数
一个macros是编译时运行的代码,可以随意调用任何普通的函数或macros。 这不仅仅是一个漂亮的filter,而且还有一些参数,应用一些转换,然后向编译器提供结果的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
,但是,因为在第一遍中,所有的macros都被扩展了,所以不存在运行时惩罚 !
Lisp> (macroexpand-1 '(my-when (numberp 1) (print "yay!"))) (if (numberp 1) (progn (print "yay!")))
请注意, macroexpand-1
只做一个扩展级别; 这是可能的(最有可能的,事实上!),扩大继续下去。 然而,最终你会碰到编译器特定的实现细节,这往往不是很有趣。 但是,不断扩大结果最终会让你获得更多的细节,或者只是你的inputs-exp。
希望澄清事情。 macros是一个强大的工具,并且是我喜欢的Lisp中的一个特性。
我可以想到的最好的例子是Paul Lisham的On Grap的书。 完整的PDF可以从我刚刚给的链接下载。 你也可以尝试Practical Common Lisp (也可以在网上find)。
我有很多不切实际的例子。 我曾经写过一个约40行的lisp程序,它可以parsing自己,把它的源代码作为一个lisp列表,做一个树遍历的列表,并build立一个expression式,如果waldo标识符存在于源代码中或者评估为如果waldo不存在,则为零。 返回的expression式是通过将car / cdr调用添加到被parsing的原始源来构造的。 我不知道如何用40行代码在其他语言中做到这一点。 也许perl可以做更less的行。
您可能会发现这篇文章有帮助: http : //www.defmacro.org/ramblings/lisp.html
也就是说,要给出Lisp权力的简短,实用的例子是非常非常困难的,因为它仅仅在非平凡的代码中发光。 当你的项目增长到一定的规模时,你会欣赏Lisp的抽象设施,并且很高兴你已经使用它们。 另一方面,合理的短代码样本从来不会给你一个让Lisp更好的示范,因为其他语言的预定义缩写在小范例中看起来比Lisp在pipe理域特定抽象方面的灵活性更具吸引力。
其实,一个很好的实例就是Lisp LOOPmacros。
http://www.ai.sri.com/pkarp/loop.html
LOOPmacros就是这样 – 一个Lispmacros。 然而,它基本上定义了一个迷你循环DSL(域特定语言)。
当你浏览这个小教程的时候,你可以看到(甚至作为一个新手),很难知道哪部分代码是Loopmacros的一部分,哪个是“正常的”Lisp。
这是Lispperformance力的关键组成部分之一,新代码实际上无法与系统区分开来。
例如,在Java中,您可能无法(一眼就能)知道程序的哪一部分来自标准Java库,而不是您自己的代码,甚至是第三方库,您知道代码的哪一部分是Java语言而不是简单的类方法调用。 当然,这是所有的“Java语言”,但作为程序员,你只能将你的应用程序表示为类和方法(现在是注释)的组合。 而在Lisp中,从字面上来说,一切都是为了争夺。
考虑将Common Lisp连接到SQL的通用SQL接口。 在这里, http://clsql.b9.com/manual/loop-tuples.html ,它们展示了如何扩展CL Loopmacros以使SQL绑定成为“头等公民”。
您还可以观察“[select [first-name] [last-name]:from [employee]:order-by [last-name]]”等构造。 这是CL-SQL包的一部分,并作为“阅读器macros”实现。
在Lisp中,不仅可以使macros创build新的构造,如数据结构,控制结构等,而且甚至可以通过读取器macros来更改语言的语法。 在这里,他们正在使用一个阅读器macros(在这种情况下,'['符号)放入SQL模式,使SQL像embedded式SQL一样工作,而不是像许多其他语言那样只是原始string。
作为应用程序开发人员,我们的任务是将我们的stream程和结构转换为处理器可以理解的forms。 这意味着我们不可避免地要“讲话”计算机语言,因为它“不了解”我们。
Common Lisp是less数几个我们不仅可以自上而下构build应用程序的环境之一,而且还可以让语言和环境提升到一半。 我们可以在两端编码。
记住,尽可能优雅,这不是万能的。 显然还有其他因素会影响语言和环境的select。 但是,这是值得学习和玩的。 我认为学习Lisp是推进编程的好方法,即使在其他语言中也是如此。
我喜欢Common Lisp Object System (CLOS)和multimethods。
大多数(如果不是全部的话)面向对象的编程语言具有类和方法的基本概念。 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
方法放在peel
工具上,让香蕉接受它。 但是,它必须属于PeelingTool类,因此只有在拥有PeelingTool类的实例时才能使用它。
Common Lisp对象系统版本:
(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)))
任何东西都可以用图灵完成的任何语言编写; 语言之间的区别在于你需要跳过多less圈来获得相同的结果。
像Common Lisp这样的function强大的语言,具有macros和CLOS等function,可以让您快速轻松地获得结果,而无需跳过如此多的箍环,从而解决您的问题,或者成为一只袋鼠。
Lisp有许多杀手级的function,但macros是我特别喜欢的,因为在语言定义和我所定义的内容之间不存在真正的障碍。 例如,Common Lisp没有一个while结构。 我曾经在脑海中执行它,而走路。 它简单明了:
(defmacro while (condition &body body) `(if ,condition (progn ,@body (do nil ((not ,condition)) ,@body))))
Etvoilà! 你只是用一个新的基本结构扩展了Common 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 ++
这篇文章的作者Brandon Corfman写了一篇研究,将Java,C ++和Lisp的解决scheme与编程问题进行比较,然后用C ++编写他自己的解决scheme。 基准解决scheme是Peter Norvig的45行Lisp(写在2个小时内)。
Corfman发现很难将他的解决scheme减less到less于142行的C ++ / STL。 他分析为什么,是一个有趣的阅读。
我最喜欢Lisp(和Smalltalk )系统的东西是他们感觉活着。 您可以在运行时轻松探测和修改Lisp系统。
如果这听起来很神秘,请启动Emacs ,然后键入一些Lisp代码。 键入CMx
和voilà! 你只是从Emacs中改变了Emacs。 您可以继续并在运行时重新定义所有Emacsfunction。
另一件事是代码=列表等价性使代码和数据之间的边界非常薄。 而且,感谢macros,扩展语言和制作快速的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代码中,自动缩进使代码看起来像输出,除了没有任何结束标记。
我喜欢这个来自http://common-lisp.net/cgi-bin/viewcvs.cgi/cl-selenium/?root=cl-selenium的macros例子。它是一个与Selenium(一个Web浏览器testing框架)的通用Lisp绑定,但是不是映射每个方法,而是在编译时读取Selenium自己的API定义XML文档,并使用macros生成映射代码。; 你可以在这里看到生成的API:common-lisp.net/project/cl-selenium/api/selenium-package/index.html
这本质上是驱动着外部数据的macros,在这种情况下恰好是一个XML文档,但是从数据库或networking读取数据可能是一件非常复杂的事情。 这是在编译时使整个Lisp环境可用的能力。
了解如何使用XML模板扩展Common Lisp : 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的反向阅读器(用于列表准引用)基本上是一样的,但是它也适用于XML(安装在特殊的<>语法),JavaScript(安装在js-inline)等等。
为了说清楚,这是在用户库中实现的 ! 它将静态的XML,JavaScript等等部分编译成UTF-8编码的文字字节数组,以准备写入networkingstream。 用一个简单的(逗号),你可以重新把运行时生成的数据join到字面字节数组中。
这不是为了模糊,但是这是图书馆把上面的内容编译成:
(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+)
作为参考,当转换为string时,上面的两个大字节向量看起来像这样:
"<div constantAttribute=\"42\" someJavaScript=\"javascript: print((40 + 2))\" runtimeAttribute=\""
第二个:
"\"> <someRandomElement> <someOther/> </someRandomElement> </div>"
它与其他Lisp结构(如macros和函数)很好地结合在一起。 现在,将其与JSP进行比较…
20世纪70年代,我是麻省理工学院的一名AI学生。 和其他每个学生一样,我认为语言是最重要的。 不过,Lisp是主要的语言。 这些是我仍然认为这是非常好的一些事情:
-
符号math。 写一个expression式的符号区分和代数简化是容易和有益的。 我仍然这样做,即使我用C语言来做。
-
定理certificate。 每隔一段时间,我都会进行临时的AI狂欢,就像试图certificate插入sorting是正确的一样。 为此我需要做象征性的操作,而且我通常会回到Lisp上。
-
小领域特定语言。 我知道Lisp并不是很实用,但是如果我想尝试一下DSL,而不必将所有内容都parsing出来,Lispmacros就可以轻松实现。
-
像minimax游戏树search一样的小游戏algorithm可以用三行来完成。
- 想要尝试lambda微积分 ? Lisp很简单。
Lisp为我做的主要是心理锻炼。 然后我可以把它转换成更实用的语言。
PS说到lambda演算,在70年代也开始了,在同一个AI millieu中,面向对象开始侵入每个人的大脑,不知何故,它似乎对它的好处感兴趣。 即在机器学习,自然语言,视觉,问题解决等各种工作中,class级,消息,types,多态等等都走到了房间的后面。
我喜欢的一件事是,我可以升级代码“运行时”,而不会丢失应用程序状态。 这只是在某些情况下才有用的东西,但是当它有用时,让它在那里已经存在(或者在开发过程中只花费最小的代价)比从零开始实现它便宜得多。 特别是因为这是“没有几乎没有”的成本。
你看过这个macros为什么强大而灵活的解释吗? 虽然没有其他语言的例子,对不起,但它可能会卖给你的macros。
@标记,
尽pipe你所说的话有一些道理,但我相信这并不总是那么简单。
程序员和一般人并不总是花时间来评估所有的可能性,并决定切换语言。 经常是决定的pipe理者,或教授第一语言的学校……程序员从来没有必要投入足够的时间去达到一定的水平,因为他们可以决定这种语言比我的语言节省了更多的时间。
另外,你必须承认,与没有这种支持的语言相比,那些拥有巨大商业实体(如微软或者Sun)支持的语言在市场上永远占有优势。
为了回答原来的问题,保罗·格雷厄姆试图在这里举一个例子,即使我承认它不一定像我想的那样实际 🙂
给我留下深刻印象的是,如果你碰巧不喜欢附带的CLOS,就可以编写自己的面向对象编程扩展。
其中一人在加内特 ,一人在保罗·格雷厄姆的On Lisp 。
还有一个名为Screamer的软件包,允许非确定性的编程(我还没有评估过)。
任何允许你改变它来支持不同的编程范例的语言都必须是灵活的。
你可能会发现埃里克·诺曼德这个职位很有帮助。 他描述了随着代码库的增长,Lisp帮助您将语言构build到您的应用程序。 虽然这通常需要额外的努力,但是之后会给您带来很大的好处。
这是一个多范式语言这个简单的事实使得它非常灵活。
1994年,John Ousterhout就Lisp提出了一个有趣的观点:
语言devise者喜欢争论为什么这种语言或那种语言必须先天好或坏,但这些论据都不重要。 当用户用脚投票时,最终所有的语言问题都会得到解决。
如果[语言]使人们更富有成效,那么他们会使用它; 当其他语言出现时更好(或者如果已经在这里),那么人们就会切换到这种语言。 这是法律,这是好的。 法律对我说,Scheme(或任何其他Lisp方言)可能不是“正确的”语言:在过去的30年里,有太多人用脚投了票。