Common Lisp: (NULL L) 应该是一个 Lambda 表达式

Log*_*der 0 lisp lambda common-lisp

我最近一直在尝试学习 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))))
Run Code Online (Sandbox Code Playgroud)

Rai*_*wig 5

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

(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))))
Run Code Online (Sandbox Code Playgroud)

do的语法是:

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

这给了我们一个提示,您的cond位置错误,并且预期是一个以结束测试形式作为其第一个元素的列表:

(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))))
Run Code Online (Sandbox Code Playgroud)

你为什么看到那个错误信息?

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

(do ((L library (cdr L)))           ; iterating
    (cond ((NULL L) result) ...)    ; end-test + result forms
Run Code Online (Sandbox Code Playgroud)

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

    (cond               ; the test is just a variable reference
     ((NULL L) result)  ; result form number one
     ...)
Run Code Online (Sandbox Code Playgroud)

现在测试看起来仍然有效,但第一个结果形式不是:

((NULL L) result)
Run Code Online (Sandbox Code Playgroud)

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

((lambda (a b) (+ a b 10)) 12 20)
Run Code Online (Sandbox Code Playgroud)

因此,您的 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)))))
Run Code Online (Sandbox Code Playgroud)

另一个问题:library是一个全局变量。这些写成*library*并由DEFPARAMETERor定义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)))))
Run Code Online (Sandbox Code Playgroud)

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

(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)))))
Run Code Online (Sandbox Code Playgroud)

下一个改进是风格:car并且cdr是老式的,用于 cons 单元操作。如果我们处理列表,我们使用firstand 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)))))
Run Code Online (Sandbox Code Playgroud)

我们可以试试:

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

CL-USER 152 > (retrieve-by 'title "Johann Holtrop")
(JOHANN-HOLTROP)
Run Code Online (Sandbox Code Playgroud)

简化代码

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)))))
Run Code Online (Sandbox Code Playgroud)

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

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

风格规则

重复上面的样式规则:

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