Apakah '(a. B) benar-benar daftar?

15

Saya benar-benar bingung dengan .notasi. Apakah '(a . b)daftar?

(listp '(a . b))kembali ttetapi ketika saya ingin tahu panjangnya (length '(a . b))memberikan kesalahan Wrong type argument: listp, b. Hal yang sama untuk fungsi lain seperti nth,mapcardll. Mereka semua memberikan kesalahan yang sama

Apakah ada fungsi yang bisa saya bedakan antara '(a b)dan '(a . b)?


Konteks: Saya mengalami masalah ini ketika saya ingin mengimplementasikan versi rekursif dari mapcar. Ini implementasi saya

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

Saya menggunakan ini untuk mengekstrak semua tag tertentu dari html parsed. Contoh htmluntuk menguraikan

;; 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>

Lalu saya mengekstrak semua <td>sebagai

(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
    )
  )
tom
sumber
1
Tidak ada true-list-pdi Elisp hanya karena belum ditemukan cukup berguna untuk menyediakannya. Memang, saya tidak ingat kapan terakhir kali saya ingin menguji apakah daftar itu benar, jadi mungkin jika Anda memberi kami sedikit lebih banyak informasi tentang kasus penggunaan Anda, kami dapat membantu Anda memecahkan masalah Anda dengan cara lain.
Stefan
@Stefan Singkatnya, saya ingin menerapkan mapcar rekursif, saya mengevaluasi fungsi yang diberikan pada elemen daftar yang diberikan, kemudian pada elemen elemen dari daftar, kemudian pada elemen elemen elemen dari daftar dan sebagainya. Jadi saya perlu tahu apakah suatu elemen adalah daftar yang benar atau tidak.
tom
Ini berguna misalnya ketika saya mengurai html dengan libxml-parse-html-regiondan saya ingin mengekstrak semua <td>tag.
tom
Dapatkah Anda menunjukkan kepada kami contoh konkret di mana Anda mungkin mendapatkan daftar yang tepat, atau daftar yang tidak tepat, atau yang lainnya, dan di mana Anda perlu menangani 3 kasus secara berbeda? Dalam kebanyakan kasus saya harus berurusan dengan, kasus "tepat" dan "tidak tepat" dapat dibagikan sampai kita sampai pada ekor yang tidak tepat yang sebenarnya, sehingga Anda lagi tidak perlu menguji apakah itu benar atau tidak: hanya menguji jika itu conspmalah.
Stefan
1
libxml tidak hanya mengembalikan daftar-daftar. Setiap daftar yang mewakili elemen XML memiliki formulir (atribut simbol. Konten). Jadi kode Anda seharusnya tidak menerapkan mapcar secara rekursif atas semua elemen daftar, hanya di atas cddrdaftar (untuk melewati nama elemen dan atribut). Setelah Anda melakukannya, Anda harus menemukan bahwa semua daftar sudah benar dan masalah Anda akan hilang. Ini juga akan memperbaiki bug dalam kode Anda di mana Anda dapat mengacaukan tdatribut untuk tdelemen.
Stefan

Jawaban:

22

Itu memuaskan listp, jadi dalam arti itu adalah daftar. listphanya menguji apakah ada yang kontra atau nil(alias ()), di satu sisi, atau sesuatu yang lain, di sisi lain.

Sebuah daftar yang tepat atau daftar yang benar (atau daftar yang tidak daftar titik-titik atau daftar melingkar) adalah sesuatu yang listpdan juga memiliki nilsebagai cdr terakhir. Artinya, daftar XSadalah tepat jika (cdr (last XS))adalah nil(dan itu adalah bagaimana Anda membedakannya).

Cara lain untuk menempatkan ini adalah bahwa daftar yang tepat memiliki daftar yang tepat sebagai cdr-nya . Ini adalah cara tipe data (benar) Daftar didefinisikan dalam bahasa yang diketik. Ini adalah definisi tipe generik dan rekursif: Bagian generik mengatakan bahwa argumen pertama ke konstruktor daftar tidak kosong (sering disebut cons, BTW) dapat dari jenis apa pun. Bagian rekursif mengatakan bahwa argumen kedua adalah turunan dari tipe (benar) Daftar .

Ya, Anda memeriksa apakah daftar yang diberikan listpbenar atau (cdr (last XS))tidak nil. Untuk memeriksa apakah cdr makhluk itu sendiri adalah daftar yang tepat, Anda harus terus memeriksa cdrnya, hingga akhir - kontra terakhir, untuk melihat apakah itu nil. Anda dapat menetapkan predikat untuk ini sebagai berikut, jika Anda ingin:

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

Meskipun daftar bundar tidak memiliki akhir, Emacs (dimulai dengan Emacs 24) cukup pintar untuk memeriksa lastdengan benar, sehingga kode ini berfungsi bahkan untuk daftar melingkar (tetapi hanya untuk Emacs 24.1 dan yang lebih baru; untuk versi sebelumnya Anda mendapatkan rekursi "tak terbatas" sampai stack overflow).

Anda dapat menggunakan fungsi seperti lengthhanya pada daftar yang benar dan urutan lainnya. Lihat juga fungsi safe-length.

Lihat manual Elisp, Sel Cons node .

Adapun notasi, (a b)hanya gula sintaksis untuk (a . (b . nil))- lihat manual Elisp, Notasi Dotted Pair

Drew
sumber
Apa praktik terbaik untuk memeriksa daftar yang tepat? Memeriksa apakah (cdr (last XS))ini niladalah crumblesome. Apakah tidak ada fungsi seperti itu proper-list-p?
tom
@ Tom: Itu, atau sesuatu yang setara, diperlukan - Anda harus memeriksa sel kontra terakhir. Saya telah menambahkan lebih banyak tentang ini dalam jawaban sekarang.
Drew
@Drew Saya akan mengubah tubuh fungsi ke (unless (atom x) (not (cdr (last x))))Jadi Anda bahkan dapat menelepon (true-list-p "text")dan niltidak mendapatkan kesalahan.
tom
@tom: Benar; Terima kasih. Sebenarnya, itu harus diuji terlebih dahulu untuk memastikan itu adalah kontra atau nil(yaitu, listp). (Juga, FWIW, saya tidak menggunakan unlessatau whenuntuk nilai mereka kembali saya gunakan. and, orDan ifuntuk itu.)
Drew