有人试图将Lisp卖给我,这是一种超级强大的语言,可以完成所有工作,然后再做一些。
是否有一个实际的Lisp的电源的代码示例?
(最好与以常规语言编码的等效逻辑一起使用。)
有人试图将Lisp卖给我,这是一种超级强大的语言,可以完成所有工作,然后再做一些。
是否有一个实际的Lisp的电源的代码示例?
(最好与以常规语言编码的等效逻辑一起使用。)
Answers:
我喜欢宏。
这是用于从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绑定”视为一个局部变量,该变量在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 / C ++中发现的宏完全不同:在这里,您可以运行生成Lisp代码的实际Lisp代码(而不是#define
cpp中的绒毛),而在另一个之前代码被编译。宏可以使用任何真实的Lisp代码,即普通函数。基本上没有限制。
因此,让我们看看这是如何完成的。为了替换一个属性,我们定义了一个函数。
(defun ldap-attr (entity attr)
`(,attr (car (ldap:attr-value ,entity ',attr))))
反引号语法看上去有些毛茸茸,但是它很容易。当你调用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-exp。
基本上,它使您可以使用可以在问题域中找到的动词来编写代码,而不是使用该语言中的低级原语!作为一个愚蠢的例子,请考虑以下内容(如果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功能之一。
您可能会发现这篇文章很有帮助:http : //www.defmacro.org/ramblings/lisp.html
就是说,很难,简短地给出Lisp功能的实际示例,因为Lisp仅在非平凡的代码中才能发挥作用。当您的项目增长到一定大小时,您将欣赏Lisp的抽象工具,并为使用它们感到高兴。另一方面,合理的短代码示例将永远不会给您令人满意的演示,说明Lisp的出色之处,因为其他语言的预定义缩写在小示例中看起来比Lisp在管理特定于领域的抽象上的灵活性更具吸引力。
实际上,一个很好的实际例子是Lisp LOOP宏。
http://www.ai.sri.com/pkarp/loop.html
LOOP宏就是Lisp宏。然而,它基本上定义了一种小型循环DSL(域特定语言)。
当您浏览该小教程时,您会发现(即使是新手)也很难知道代码的哪一部分是Loop宏的一部分,而哪一部分是“正常的” Lisp。
这是Lisps表达能力的关键组成部分之一,新的代码实际上无法与系统区分开。
例如,在使用Java时,您可能(乍看之下)无法知道程序的哪一部分来自标准Java库,而不是您自己的代码,甚至是第三方库,您也确实知道代码的哪一部分是Java语言,而不是简单地对类进行方法调用。当然,所有这些都是“ Java语言”,但是作为程序员,您只能将应用程序表示为类和方法(以及现在的注释)的组合。而在Lisp中,几乎所有内容都可以争夺。
考虑将Common Lisp连接到SQL的Common SQL接口。这里, http://clsql.b9.com/manual/loop-tuples.html,它们显示了如何扩展CL Loop宏以使SQL绑定成为“一流公民”。
您还可以观察到诸如“ [选择[名字] [姓氏]:从[雇员] ::按[姓氏]排列]]”的构造。这是CL-SQL程序包的一部分,并作为“阅读器宏”实现。
在Lisp中,您不仅可以制作宏来创建新的结构,例如数据结构,控制结构等。而且您甚至可以通过阅读器宏来更改语言的语法。在这里,他们使用阅读器宏(在这种情况下为'['符号)进入SQL模式,以使SQL像嵌入式SQL一样工作,而不是像许多其他语言一样仅用作原始字符串。
作为应用程序开发人员,我们的任务是将我们的流程和构造转换为处理器可以理解的形式。这意味着我们不可避免地不得不“谈论”计算机语言,因为它“无法理解”我们。
Common Lisp是为数不多的环境之一,在该环境中,我们不仅可以自上而下地构建应用程序,还可以提升语言和环境,以使我们半途而废。我们可以在两端进行编码。
介意,尽其优雅,这不是万能药。显然,还有其他因素会影响语言和环境选择。但这当然值得学习和使用。我认为学习Lisp是提高编程水平的好方法,即使使用其他语言也是如此。
我喜欢通用Lisp对象系统(CLOS)和多方法。
大多数(如果不是全部)面向对象的编程语言都具有类和方法的基本概念。Python中的以下代码段定义了PeelingTool和Vegetable类(类似于Visitor模式):
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对象系统版本:
(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)))
可以用图灵完整的任何语言编写任何内容;语言之间的差异是您必须跳过多少圈才能获得同等的结果。
诸如Common Lisp之类的功能强大的语言具有宏和CLOS之类的功能,可让您快速而轻松地获得结果,而不必经历很多麻烦而无法满足于劣等解决方案,或者发现自己成为袋鼠。
Lisp中有很多杀手级功能,但是宏是我特别喜欢的宏,因为在语言定义和定义之间不再存在障碍。例如,Common Lisp没有while结构。我曾经在走路时脑海里实现了它。简单明了:
(defmacro while (condition &body body)
`(if ,condition
(progn
,@body
(do nil ((not ,condition))
,@body))))
等等!您只是使用新的基本结构扩展了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语言来完成此操作...
我发现这篇文章很有趣:
本文的作者Brandon Corfman写了一篇研究,该研究将Java,C ++和Lisp中的解决方案与编程问题进行了比较,然后编写了自己的C ++解决方案。基准解决方案是Peter Norvig的45行Lisp(在2小时内编写)。
Corfman发现很难将其解决方案减少到少于142行C ++ / STL。他对原因的分析很有趣。
我最喜欢Lisp(和Smalltalk)系统的地方是,它们感觉很活跃。您可以在Lisp系统运行时轻松地对其进行探测和修改。
如果这听起来很神秘,请启动Emacs,然后输入一些Lisp代码。输入C-M-x
并贴上!您只是从Emacs内部更改了Emacs。您可以继续运行,然后重新定义其所有Emacs功能。
另一件事是,代码=列表的等效性使代码和数据之间的边界非常稀疏。而且由于有了宏,扩展语言和制作快速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 的宏示例。这是对Selenium(Web浏览器测试框架)的Common Lisp绑定,但是它没有映射每个方法,而是在编译时读取Selenium自己的API定义XML文档,并使用宏生成映射代码。您可以在此处查看生成的API:common-lisp.net/project/cl-selenium/api/selenium-package/index.html
这实际上是在驱动带有外部数据的宏,在这种情况下,外部数据恰好是XML文档,但是从数据库或网络中读取数据可能非常复杂。这是在编译时为您提供整个Lisp环境的力量。
了解如何使用XML模板扩展Common Lisp:cl-准报价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编码的立即字节数组,这些数组可随时写入网络流。用简单,
(逗号),您可以回到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年代是麻省理工学院的AI学生。像其他所有学生一样,我认为语言至关重要。尽管如此,Lisp是主要语言。这些是我仍然认为非常适合的一些事项:
符号数学。编写表达式的符号差异和代数简化是容易且有启发性的。即使我用C语言做它们,我仍然会做那些。
定理证明。我不时地进行临时的AI狂欢,就像试图证明插入排序是正确的。为此,我需要进行符号操作,通常我会回到Lisp。
特定领域的语言很少。我知道Lisp并不是很实用,但是如果我想尝试一点DSL而不必全都解析等,那么Lisp宏就可以轻松实现。
像minimax游戏树搜索之类的小游戏算法可以在三行中完成。
Lisp为我做的主要是心理锻炼。然后,我可以将其延续到更实用的语言中。
PS说到演算,什么也开始在20世纪70年代,在同一AI millieu的,是OO开始侵入每个人的大脑,并以某种方式,它有什么兴趣是似乎它是什么已经挤占了很大的兴趣为好。即在机器学习,自然语言,视觉,解决问题等方面的工作都排到了房间的后面,而课堂,消息,类型,多态性等则排在了最前面。
John Ousterhout在1994年对Lisp提出了一个有趣的观察:
语言设计者喜欢争论为什么这种语言或某种语言 必须先验地更好或更糟,但是这些论点都没有太大关系。最终,当用户用脚投票时,所有语言问题都得到解决。
如果[一种语言]使人们更有生产力,那么他们将使用它。当出现其他更好的语言(或者如果已经出现)时,人们将切换到该语言。这是法律,这很好。法律对我说,Scheme(或任何其他Lisp方言)可能不是“正确”的语言:在过去30年中,有太多人用脚投票。