J'ai essayé d'apprendre Common Lisp récemment, et c'est le processus d'apprentissage le plus douloureux et le plus lent que j'aie jamais eu avec n'importe quel langage de programmation que j'ai jamais utilisé. Cela m'énerve vraiment. Même avec Notepad ++ montrant quelles parenthèses correspondent à quoi, c'est toujours pénible.

J'ai essayé d'écrire un programme qui simule une base de données de bibliothèque. On me dit toujours "SYSTEM::%EXPAND-FORM: (NULL L) should be a lambda expression" - J'ai lu d'autres articles ici sur Stack Overflow qui disent que cela a à voir avec l'utilisation de trop de parenthèses, mais après avoir joué avec la syntaxe pendant plus d'une heure, rien ne fonctionne. J'espère que certains d'entre vous, programmeurs LISP plus expérimentés, pourront voir mon erreur de novice, espérons-le. Merci. Le code est ci-dessous.

(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))))
0
Logan Snyder 23 févr. 2020 à 23:24

1 réponse

Meilleure réponse

Votre code mieux indenté ressemble à ceci :

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

La syntaxe pour do est :

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

Ce qui nous donne un indice que votre cond est dans une mauvaise position et que l'on attend une liste avec un formulaire de test final comme premier élément :

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

Pourquoi avez-vous vu ce message d'erreur ?

"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 attendait une liste avec un test en première position et des formulaires de résultats :

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

Maintenant, le test semble toujours valide, mais le premier formulaire de résultat ne l'est pas :

((NULL L) result)

Une liste en tant que premier élément d'un formulaire n'est pas autorisée dans Common Lisp - à une exception près : une expression lambda :

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

Ainsi, votre implémentation Lisp se plaint que (null l) n'est pas une expression lambda. Ici, je pense que les messages d'erreur peuvent être améliorés dans les implémentations...

Plus de commentaires

Il y a un problème supplémentaire : result est une variable non définie. Nous devons créer une variable locale result. Cela se fait avec 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)))))

Autre problème : library est une variable globale. Ceux-ci sont écrits comme *library* et sont définis par DEFPARAMETER ou 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)))))

La prochaine amélioration est stylistique : le code est principalement écrit en minuscules et les traits de soulignement ne sont pas utilisés. Au lieu de cela, des tirets sont utilisés :

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

La prochaine amélioration est stylistique : car et cdr sont démodés et sont utilisés pour les opérations de contre-cellule. Si nous traitons des listes, nous utilisons first et rest.

Voici notre code :

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

On peut l'essayer :

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

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

Simplifier le code

Common Lisp a une forme plus simple pour parcourir une liste : 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 a également des fonctions pour trouver des éléments. Une option étrange consiste à utiliser remove avec un argument :test-not. Nous conservons tous les éléments qui satisfont à notre test en examinant une valeur clé que nous extrayons :

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

Règles de style

Pour répéter les règles de style ci-dessus :

  • rechercher la syntaxe des opérateurs spéciaux à l'aide de l'HyperSpec (ou similaire)
  • indentez votre code correctement, l'éditeur devrait le faire pour vous
  • définissez vos variables
  • les variables globales sont écrites comme *variable-name*
  • écrire principalement en minuscules
  • utilisez des tirets dans les symboles composés entre les mots : retrieve-by
  • il y a souvent des fonctions ou d'autres opérateurs, qui sont plus faciles à utiliser que l'opérateur do
  • n'utilisez pas de parenthèses pendantes
  • écrire du code compact sans trop de lignes d'espacement
5
Rainer Joswig 24 févr. 2020 à 08:22