【问题标题】:Common Lisp: (NULL L) Should Be A Lambda ExpressionCommon Lisp: (NULL L) 应该是一个 Lambda 表达式
【发布时间】:2020-02-23 20:24:07
【问题描述】:

我最近一直在尝试学习 Common Lisp,这是我用过的任何编程语言所经历过的最痛苦、最缓慢的学习过程。这真的让我很烦。即使使用 Notepad++ 显示括号对应于什么,它仍然很痛苦。

我一直在尝试编写一个模拟图书馆数据库的程序。我一直被告知"SYSTEM::%EXPAND-FORM: (NULL L) should be a lambda expression" - 我在 Stack Overflow 上读过其他帖子,说这与使用过多的括号有关,但是在玩了一个多小时的语法之后,没有任何效果。我希望你们中一些经验丰富的 LISP 程序员可以看到我希望是新手的错误。谢谢你。代码如下。

(setq library nil)

(defun add_book(bookref title author publisher)   
    (setf (get bookref 'title) title)
    (setf (get bookref 'author) author)
    (setf (get bookref 'publisher) publisher)
    (setq library (cons bookref library))
  bookref)


(defun retrieve_by (property my_value)
    (setq result nil)
    (do ((L library (cdr L)))
        (cond
            ((NULL L) result)
            (equal (get (car L) property) my_value)
                (cons (car L) result))))

【问题讨论】:

  • 我永远不记得 DO 的所有部分应该是什么,而且,无论如何,我的建议是尽可能避免显式循环。关于 FIND-IF 的解决方案怎么样?请参阅:clhs.lisp.se/Body/f_find_.htm 谓词或测试函数(我认为您可以使用任何一种方式,两者都可以)将检查属性并将其值与指定值进行比较。
  • 这是一个非常糟糕的错误信息! ((null l) ...) 是错误的语法,因为 (null l) 出现在需要函数或运算符的位置。 lambda 表达式可以用于运算符位置,但与使用函数或运算符相比,Lisp 程序很少这样做!告诉程序员一个错误的结构应该被纠正为一个很少使用的结构是误导性的。此外,不解释上下文(错误的东西出现在需要操作符或函数名的地方)是没有帮助的。

标签: lambda lisp common-lisp


【解决方案1】:

您的代码缩进效果更好,如下所示:

(defun retrieve_by (property my_value)
  (setq result nil)
  (do ((L library (cdr L)))
      (cond
       ((NULL L) result)
       (equal (get (car L) property) my_value)
       (cons (car L) result))))

do 的语法是:

do ({var | (var [init-form [step-form]])}*)
   (end-test-form result-form*)
 declaration*
 {tag | statement}*

这提示我们您的 cond 位置错误,并且预期是一个以 end-test-form 作为其第一个元素的列表:

(defun retrieve_by (property my_value)
  (setq result nil)
  (do ((L library (cdr L)))           ; iterating L
      ((NULL L) result)               ; end test + result
     (if (equal (get (car L) property) my_value)
       (push (car L) result))))

您为什么会看到该错误消息?

"SYSTEM::%EXPAND-FORM: (NULL L) should be a lambda expression"

(do ((L library (cdr L)))           ; iterating
    (cond ((NULL L) result) ...)    ; end-test + result forms

Lisp 期望在第一个位置有一个测试列表和结果形式:

    (cond               ; the test is just a variable reference
     ((NULL L) result)  ; result form number one
     ...)

现在测试看起来仍然有效,但第一个结果表单无效:

((NULL L) result)

Common Lisp 中不允许将列表作为表单的第一个元素 - 有一个例外:lambda 表达式:

((lambda (a b) (+ a b 10)) 12 20)

因此,您的 Lisp 实现抱怨 (null l) 不是 lambda 表达式。在这里我认为错误消息可以在实现中改进......

更多反馈

还有一个问题:result 是一个未定义的变量。我们需要创建一个局部变量result。这是通过let 完成的:

(defun retrieve_by (property my_value)
  (let ((result nil))
    (do ((L library (cdr L)))
        ((NULL L) result)
      (if (equal (get (car L) property) my_value)
          (push (car L) result)))))

另一个问题:library 是一个全局变量。这些写成*library*,并由DEFPARAMETER或DEFVAR定义:

(defvar *library* nil)

(defun add_book (bookref title author publisher)   
    (setf (get bookref 'title) title)
    (setf (get bookref 'author) author)
    (setf (get bookref 'publisher) publisher)
    (setq *library* (cons bookref *library*))
  bookref)

(defun retrieve_by (property my_value)
  (let ((result nil))
    (do ((L *library* (cdr L)))
        ((null L) result)
      (if (equal (get (car L) property) my_value)
          (push (car L) result)))))

下一个改进是文体:代码大多以小写形式编写,不使用下划线。而是使用连字符:

(defvar *library* nil)

(defun add-book (bookref title author publisher)   
    (setf (get bookref 'title) title)
    (setf (get bookref 'author) author)
    (setf (get bookref 'publisher) publisher)
    (setq *library* (cons bookref *library*))
  bookref)

(defun retrieve-by (property my_value)
  (let ((result nil))
    (do ((list *library* (cdr list)))
        ((null lisp) result)
      (if (equal (get (car list) property) my_value)
          (push (car list) result)))))

下一个改进是风格:car 和 cdr 是老式的,用于 cons 单元操作。如果我们处理列表,我们使用first 和rest。

这是我们的代码:

(defvar *library* nil)

(defun add-book (bookref title author publisher)   
  (setf (get bookref 'title)     title)
  (setf (get bookref 'author)    author)
  (setf (get bookref 'publisher) publisher)
  (push bookref *library*)
  bookref)

(defun retrieve-by (property my-value)
  (let ((result nil))
    (do ((list *library* (rest list)))
        ((null list) result)
      (if (equal (get (first list) property) my-value)
          (push (first list) result)))))

我们可以试试:

CL-USER 151 > (add-book 'johann-holtrop "Johann Holtrop" "Rainald Goetz" "Suhrkamp")
JOHANN-HOLTROP

CL-USER 152 > (retrieve-by 'title "Johann Holtrop")
(JOHANN-HOLTROP)

简化代码

Common Lisp 有一种更简单的形式来遍历一个列表:dolist:

(defun retrieve-by (property my-value)
  (let ((result nil))
    (dolist (bookref *library* result)
      (if (equal (get bookref property) my-value)
          (push bookref result)))))

Common Lisp 还具有查找项目的功能。一个奇怪的选择是将remove 与:test-not 参数一起使用。我们通过查看我们提取的关键值来保留所有满足我们测试的项目:

(defun retrieve-by (property my-value)
  (remove my-value *library*
          :test-not #'equal
          :key (lambda (bookref)
                 (get bookref property))))

样式规则

从上面重复样式规则:

  • 使用HyperSpec(或类似名称)查找特殊运算符的语法
  • 正确缩进你的代码,编辑器应该为你做这件事
  • 定义变量
  • 全局变量写成*variable-name*
  • 主要是小写
  • 在单词之间的复合符号中使用连字符:retrieve-by
  • 通常有函数或其他运算符,比do运算符更容易使用
  • 不要使用悬空括号
  • 编写没有太多空白行的紧凑代码

【讨论】:

  • 我不确定您的 push 是否应该是 add-book 中的 pushnew。如果不是,那么您可以在库中获得重复项。
  • @tfb:好点。我尽量不改变所提供示例的语义。
  • 标记为答案。您的回复远远超出了我的需要,但对新的 lisp 程序员都非常有帮助。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-20
  • 1970-01-01
  • 2014-07-07
  • 2012-10-24
  • 2021-08-11
  • 1970-01-01
  • 2011-02-04
相关资源
最近更新 更多