gpt4 book ai didi

lisp - Lisp 灵活性的实际例子?

转载 作者:太空宇宙 更新时间:2023-11-03 18:31:40 27 4
gpt4 key购买 nike

就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the help center为指导。




9年前关闭。




有人试图向我推销 Lisp,它是一种 super 强大的语言,可以做任何事情,然后是一些。

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

最佳答案

我喜欢宏。

这是从 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 绑定(bind)”视为局部变量,它会在 LET 形式之外消失。请注意绑定(bind)的形式——它们非常相似,不同之处仅在于 LDAP 实体的属性和要将值绑定(bind)到的名称(“局部变量”)。有用,但有点冗长并且包含重复。

追求美丽

现在,如果我们不必拥有所有这些重复,那不是很好吗?一个常见的习惯用法是 WITH-... 宏,它根据可以从中获取值的表达式绑定(bind)值。让我们引入我们自己的宏,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 代码(不是 cpp 中的 #define 绒毛),在编译其他代码之前。宏可以使用任何真正的 Lisp 代码,即普通函数。基本上没有限制。

摆脱丑陋

那么,让我们看看这是如何完成的。为了替换一个属性,我们定义了一个函数。
(defun ldap-attr (entity attr)
`(,attr (car (ldap:attr-value ,entity ',attr))))

反引号语法看起来有点麻烦,但它的作用很简单。当您调用 LDAP-ATTRS 时,它会输出一个包含 attr 值的列表。 (这是逗号),然后是 car (“列表中的第一个元素”(实际上是 cons 对),实际上还有一个名为 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 绑定(bind)与开头的代码进行比较,您会发现它的形式相同!

编译时与运行时:宏与函数

宏是在编译时运行的代码,具有额外的功能,它们可以随意调用任何普通函数或宏!它只不过是一个花哨的过滤器,接受一些参数,应用一些转换,然后将生成的 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!"))))

与我们的新 friend , 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 - Lisp 灵活性的实际例子?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/106058/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com