Common Lisp: (NULL L) должен быть лямбда-выражением - PullRequest
0 голосов
/ 23 февраля 2020

Недавно я пытался выучить Common Lisp, и это самый болезненный и медленный процесс обучения, который у меня когда-либо был с любым языком программирования, который я когда-либо использовал. Это действительно раздражает меня. Даже с Notepad ++, показывающим, какие скобки соответствуют чему, это все равно является болью.

Я пытался написать программу, которая имитирует библиотечную базу данных. Я все время получаю сообщение "SYSTEM::%EXPAND-FORM: (NULL L) should be a lambda expression" - я читал другие посты здесь о переполнении стека, в которых говорится, что это связано с использованием слишком большого количества скобок, но после игры с синтаксисом более часа ничего не работает. Я надеюсь, что некоторые из вас, более опытных программистов на 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))))

1 Ответ

5 голосов
/ 24 февраля 2020

Ваш код с лучшим отступом выглядит следующим образом:

(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 находится в неправильной позиции, и ожидается, что это список с формой окончания теста в качестве первого элемента:

(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

Лисп ожидал список с тестом в первой позиции и формой результата:

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

Теперь тест по-прежнему выглядит действительным, но первая форма результата не является:

((NULL L) result)

Список в качестве первого элемента формы недопустим в Common Lisp - за одним исключением: лямбда-выражение:

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

Таким образом, ваша реализация на Лиспе жалуется, что (null l) не является лямбда-выражением , Здесь я думаю, что сообщения об ошибках могут быть улучшены в реализациях ...

Дополнительные отзывы

Существует дополнительная проблема: 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)))))

Следующее улучшение - это stylisti c: код в основном пишется в нижнем регистре и подчеркивания не используются. Вместо этого используются дефисы:

(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)))))

Следующим улучшением является стилист c: car и cdr являются старомодными и используются для операций с ячейками "против". Если мы имеем дело со списками, мы используем 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))))

Правила стиля

Повторение правил стиля сверху:

  • ищите синтаксис специальных операторов, используя HyperSpe c (или аналогичный)
  • , корректный отступ кода, редактор должен сделать это для вас
  • определить ваши переменные
  • глобальные переменные записываются как *variable-name*
  • писать в основном строчными буквами
  • использовать дефисы в составных символах между слова: retrieve-by
  • часто существуют функции или другие операторы, которые проще в использовании, чем оператор do
  • , не используйте висячие скобки
  • пишите компактный код без лишних пробельных строк
...