'(A. B) est-il vraiment une liste?

15

Je suis vraiment confus avec la .notation. Est-ce '(a . b)une liste?

(listp '(a . b))retourne tmais quand je veux savoir sa longueur (length '(a . b))donne une erreur Wrong type argument: listp, b. La même chose est pour les autres fonctions comme nth,mapcaretc., elles donnent toutes la même erreur

Y a-t-il une fonction que je peux distinguer entre '(a b)et '(a . b)?


Contexte: J'ai rencontré ce problème lorsque j'ai voulu implémenter une version récursive de mapcar. Voici ma mise en œuvre

(defun true-listp (object)
"Return non-`nil' if OBJECT is a true list."
(and (listp object)  (null (cdr (last object)))))

(defun recursive-mapcar (func list)
"Evaluates func on elements of the list, then on elements of elements  of the list and so forth." 
(let ((output nil))
(flet ((comp (a b) nil)
       (call-fun-and-save (x) (add-to-list 'output (funcall func x) t 'comp))
       (recursion (l)
                  (mapcar
                   (lambda (x)
                     (call-fun-and-save x)
                     (if (and (true-listp x))  ;; HERE I use true-listp, testing for list or cons is not sufficient
                         (recursion x)))
                    l)))
   (recursion list))
  output))

Je l'utilise pour extraire toutes les balises spécifiques du html analysé. Exemple d' htmlanalyse

;; buffer 'html'
<html>
<body>
<table style="width:100%">
  <tr>  <td>Jill</td>  <td>Smith</td>  <td>50</td> </tr>
  <tr>  <td>Eve</td>   <td>Jackson</td>   <td>94</td> </tr>
</table>
</body>
</html>

Ensuite, j'extrait tout <td>comme

(with-current-buffer (get-buffer "html")
  (let ((data (libxml-parse-html-region (point-max) (point-min))))

    ;; gat only <td> tags
    (-non-nil
     (recursive-mapcar
      (lambda(x) (and (consp x) (equal 'td (car x)) x))
      data))
    data
    )
  )
à M
la source
1
Il n'y true-list-pen a pas dans Elisp simplement parce qu'il n'a pas été jugé suffisamment utile pour le fournir. En effet, je ne me souviens pas de la dernière fois que j'ai voulu tester si une liste était correcte, alors peut-être que si vous nous donnez un peu plus d'informations sur votre cas d'utilisation, nous pouvons vous aider à résoudre votre problème d'une autre manière.
Stefan
@Stefan En bref, je veux implémenter mapcar récursif, j'évalue une fonction donnée sur des éléments d'une liste donnée, puis sur des éléments d'éléments de la liste, puis sur des éléments d'éléments d'éléments de la liste et ainsi de suite. J'ai donc besoin de savoir si un élément est une vraie liste ou non.
Tom
C'est utile par exemple lorsque j'analyse du HTML avec libxml-parse-html-regionet que je veux extraire toutes les <td>balises.
Tom
Pouvez-vous nous montrer un exemple concret où vous pourriez obtenir soit une liste correcte, soit une liste incorrecte, soit autre chose, et où vous devez gérer les 3 cas différemment? Dans la plupart des cas auxquels j'ai dû faire face, les cas «corrects» et «impropres» peuvent être partagés jusqu'à ce que nous arrivions à la queue incorrecte réelle, vous n'avez donc plus besoin de tester si elle est correcte ou non: testez simplement si c'est à la conspplace.
Stefan
1
libxml ne renvoie pas seulement des listes de listes. Chaque liste représentant un élément XML a la forme (attributs de symbole. Contenu). Par conséquent, votre code ne doit pas appliquer mapcar de manière récursive sur tous les éléments des listes, uniquement sur cddrla liste (pour ignorer le nom de l'élément et les attributs). Une fois que vous avez fait cela, vous devriez constater que toutes les listes sont correctes et votre problème disparaîtra. Il corrigera également un bogue dans votre code où vous pouvez confondre un tdattribut pour un tdélément.
Stefan

Réponses:

22

Il satisfait listp, donc en ce sens, c'est une liste. listpteste simplement si quelque chose est contre ou nil(aka ()), d'une part, ou autre chose, d'autre part.

Une liste correcte ou une liste vraie (ou une liste qui n'est pas une liste en pointillés ou une liste circulaire) est quelque chose qui est listpet a également nilcomme dernier cdr. Autrement dit, une liste XSest appropriée si (cdr (last XS))est nil(et c'est ainsi que vous la distinguez).

Une autre façon de dire ceci est qu'une liste appropriée a une liste appropriée comme son cdr . C'est ainsi que le type de données (approprié) List est défini dans les langues tapées. Il s'agit d'une définition de type générique et récursive: la partie générique indique que le premier argument du constructeur de liste non vide (souvent appelé consBTW) peut être de n'importe quel type. La partie récursive dit que son deuxième argument est une instance de type (propre) List .

Oui, vous vérifiez si une donnée listpest une liste appropriée en utilisant (cdr (last XS))is nil. Afin de vérifier si le cdr de la créature est lui-même une liste appropriée, vous devez continuer à vérifier son cdr, jusqu'à la fin - le dernier inconvénient, pour voir s'il l'est nil. Vous pouvez définir un prédicat pour cela comme suit, si vous le souhaitez:

(defun true-listp (object)
  "Return non-`nil' if OBJECT is a true list."
  (and (listp object)  (null (cdr (last object)))))

Bien qu'une liste circulaire n'ait pas de fin, Emacs (commençant par Emacs 24) est suffisamment intelligent pour vérifier lastcorrectement, donc ce code fonctionne même pour une liste circulaire (mais uniquement pour Emacs 24.1 et versions ultérieures; pour les versions antérieures, vous obtenez une récursion "infinie" jusqu'au débordement de la pile).

Vous pouvez utiliser des fonctions telles que lengthuniquement sur des listes appropriées et d'autres séquences. Voir aussi fonction safe-length.

Voir le manuel Elisp, node Cons Cells .

Quant à la notation, (a b)c'est juste du sucre syntaxique pour (a . (b . nil))- voir le manuel Elisp, noeud Notation par paire pointée

A dessiné
la source
Quelle est la meilleure pratique pour vérifier la liste appropriée? Vérifier si (cdr (last XS))est nilest crumblesome. N'y a-t-il pas une fonction comme proper-list-p?
tom
@tom: Cela, ou quelque chose d'équivalent, est nécessaire - vous devez vérifier la dernière cellule contre. J'ai ajouté plus à ce sujet dans la réponse maintenant.
Drew
@Drew Je changerais le corps de la fonction pour que (unless (atom x) (not (cdr (last x))))vous puissiez même appeler (true-list-p "text")et nilne pas avoir d'erreur.
tom
@tom: Droite; THX. En fait, il doit d'abord être testé pour s'assurer qu'il est contre ou nil(c.-à listp-d.). ( De plus, FWIW, je ne pas utiliser unlessou whenpour leur valeur de retour , je l' utilise. and, orEt ifpour cela.)
Drew