Lisp灵活性的实际例子? [英] Practical example of Lisp's flexibility?

查看:76
本文介绍了Lisp灵活性的实际例子?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人试图将Lisp卖给我,这是一种超级强大的语言,可以完成所有工作,然后再做一些.

Someone is trying to sell Lisp to me, as a super powerful language that can do everything ever, and then some.

是否有一个Lisp功能的实用代码示例?
(最好与使用常规语言编码的等效逻辑一起使用)

Is there a practical code example of Lisp's power?
(Preferably alongside equivalent logic coded in a regular language.)

推荐答案

我喜欢宏.

这里的代码可以填充LDAP中人们的属性.我只是碰巧发现该代码四处走动而变了,对其他人有用.

Here's code to stuff away attributes for people from LDAP. I just happened to have that code lying around and fiigured it'd be useful for others.

某些人对宏的假定运行时惩罚感到困惑,因此我在末尾进行了一些尝试以弄清事情.

Some people are confused over a supposed runtime penalty of macros, so I've added an attempt at clarifying things at the end.

(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实体的属性和绑定值的名称(局部变量")不同.有用,但有点冗长,并且包含重复项.

You can think of a "let binding" as a local variable, that disappears outside the LET form. Notice the form of the bindings -- they are very similar, differing only in the attribute of the LDAP entity and the name ("local variable") to bind the value to. Useful, but a bit verbose and contains duplication.

现在,如果我们不必进行所有重复操作,那岂不是很好吗?一个常见的习惯用法是WITH -...宏,它基于可以从中获取值的表达式来绑定值.让我们介绍一下自己的宏,WITH-LDAP-ATTRS,然后将其替换为我们的原始代码.

Now, wouldn't it be nice if we didn't have to have all that duplication? A common idiom is is WITH-... macros, that binds values based on an expression that you can grab the values from. Let's introduce our own macro that works like that, WITH-LDAP-ATTRS, and replace it in our original code.

(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 ++中发现的宏完全不同:在这里,您可以运行 real Lisp代码(不是 cpp)生成Lisp代码,然后再编译其他代码.宏可以使用任何真实的Lisp代码,即普通函数.基本上没有限制.

Did you see how a bunch of lines suddenly disappeared, and was replaced with just one single line? How to do this? Using macros, of course -- code that writes code! Macros in Lisp is a totally different animal than the ones you can find in C/C++ through the use of the pre-processor: here, you can run real Lisp code (not the #define fluff in cpp) that generates Lisp code, before the other code is compiled. Macros can use any real Lisp code, i.e., ordinary functions. Essentially no limits.

所以,让我们看看这是如何完成的.要替换一个属性,我们定义一个函数.

So, let's see how this was done. To replace one attribute, we define a function.

(defun ldap-attr (entity attr)
  `(,attr (car (ldap:attr-value ,entity ',attr))))

反引号语法看上去有些毛茸茸,但是这样做很容易.当您调用LDAP-ATTRS时,它将吐出一个包含attr(即逗号)的 value 的列表,然后是car(列表中的第一个元素"(实际上),并且实际上还有一个名为first的函数也可以使用),该函数接收ldap:attr-value返回的列表中的第一个值.因为这不是我们在编译代码时要运行的代码(获取属性值是运行程序时要执行的操作),所以在调用之前不必添加逗号

The backquote syntax looks a bit hairy, but what it does is easy. When you call LDAP-ATTRS, it'll spit out a list that contains the value of attr (that's the comma), followed by car ("first element in the list" (cons pair, actually), and there is in fact a function called first you can use, too), which receives the first value in the list returned by ldap:attr-value. Because this isn't code we want to run when we compile the code (getting the attribute values is what we want to do when we run the program), we don't add a comma before the call.

无论如何.移动到宏的其余部分.

Anyway. Moving along, to the rest of the macro.

(defmacro with-ldap-attrs (attrs ent &rest body)
  `(let ,(loop for attr in attrs
         collecting `,(ldap-attr ent attr))
     ,@body)) 

,@语法是将列表的内容放在某个地方,而不是实际的列表.

The ,@-syntax is to put the contents of a list somewhere, instead of the actual list.

您可以轻松地验证这将为您提供正确的东西.宏通常以这种方式编写:首先从要简化的代码(输出)开始,到要编写的代码(输入)开始,然后开始模制宏,直到输入提供正确的输出为止.函数macroexpand-1会告诉您您的宏是否正确:

You can easily verify that this will give you the right thing. Macros are often written this way: you start off with code you want to make simpler (the output), what you want to write instead (the input), and then you start molding the macro until your input gives the correct output. The function macroexpand-1 will tell you if your macro is correct:

(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绑定与开头的代码进行比较,您会发现它的格式相同!

If you compare the LET-bindings of the expanded macro with the code in the beginning, you'll find that it is in the same form!

宏是在编译时运行的代码,其附加的功能是它们可以随意调用任何 ordinary 函数或宏!它只是一个花哨的过滤器,它带有一些参数,应用了一些转换,然后向编译器提供了生成的s-exps.

A macro is code that is run at compile-time, with the added twist that they can call any ordinary function or macro as they please! It's not much more than a fancy filter, taking some arguments, applying some transformations and then feeding the compiler the resulting s-exps.

基本上,它使您可以使用可以在问题域中找到的动词来编写代码,而不是使用该语言中的低级原语!作为一个愚蠢的示例,请考虑以下内容(如果when还不是内置的):

Basically, it lets you write your code in verbs that can be found in the problem domain, instead of low-level primitives from the language! As a silly example, consider the following (if when wasn't already a built-in)::

(defmacro my-when (test &rest body)
  `(if ,test 
     (progn ,@body)))

if是一个内置基元,将仅允许您在分支中执行一个表单,如果要多个,请使用progn ::

if is a built-in primitive that will only let you execute one form in the branches, and if you want to have more than one, well, you need to use 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 ::

With our new friend, my-when, we could both a) use the more appropriate verb if we don't have a false branch, and b) add an implicit sequencing operator, i.e. progn::

(my-when (numberp 1)
  (assert-world-is-sane t)
  (print "phew!"))

但是,编译后的代码将永远不会包含my-when,因为在第一遍中,所有宏都被扩展了,因此不会涉及运行时的损失!

The compiled code will never contain my-when, though, because in the first pass, all macros are expanded so there is no runtime penalty involved!

Lisp> (macroexpand-1 '(my-when (numberp 1)
                        (print "yay!")))

(if (numberp 1)
  (progn (print "yay!")))

请注意,macroexpand-1仅执行一级扩展; (很可能,实际上是!)扩展可能会进一步下降.但是,最终您会遇到特定于编译器的实现细节,这些细节通常不是很有趣.但是继续扩展结果最终将为您提供更多详细信息,或者只是向您提供输入s-exp.

Note that macroexpand-1 only does one level of expansions; it's possible (most likely, in fact!) that the expansion continues further down. However, eventually you'll hit the compiler-specific implementation details which are often not very interesting. But continuing expanding the result will eventually either get you more details, or just your input s-exp back.

希望可以澄清一些事情.宏是一种功能强大的工具,也是我喜欢的Lisp功能之一.

Hope that clarifies things. Macros is a powerful tool, and one of the features in Lisp I like.

这篇关于Lisp灵活性的实际例子?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆