Przejrzałem On Lisp, Practical Common Lisp i archiwa SO, aby samodzielnie odpowiedzieć na to pytanie, ale te próby były sfrustrowane moją niemożnością nazwania koncepcji Jestem zainteresowany. Byłbym wdzięczny, gdyby ktoś mógł mi podać kanoniczny termin na tego typu rzeczy.

To pytanie prawdopodobnie najlepiej wyjaśni przykład. Powiedzmy, że chcę zaimplementować listy składane w stylu Pythona w Common Lisp. W Pythonie napisałbym:

[x*2 for x in range(1,10) if x > 3]

Więc zaczynam od spisania:

(listc (* 2 x) x (range 1 10) (> x 3))

A następnie zdefiniowanie makra, które przekształca powyższe w poprawne zrozumienie. Jak na razie dobrze.

Interpretacja tego wyrażenia byłaby jednak niejasna dla czytelnika, który nie zna jeszcze wyrażeń listowych w Pythonie. To, co naprawdę chciałbym móc napisać, to:

(listc (* 2 x) for x in (range 1 10) if (> x 3)) 

Ale nie byłem w stanie wyśledzić terminologii Common Lisp dla tego. Wygląda na to, że makro loop robi dokładnie takie rzeczy. Jak to się nazywa i jak mogę to zaimplementować? Próbowałem makrorozszerzenia przykładowego wyrażenia pętli, aby zobaczyć, jak to jest złożone, ale wynikowy kod był niezrozumiały. Czy ktoś mógłby mnie poprowadzić we właściwym kierunku?

Z góry dziękuję.

3
wvoq 20 sierpień 2011, 09:00
Listy składane są już dostępne z okropnym makrem LOOP.
 – 
SK-logic
20 sierpień 2011, 14:23
1
Ja wiem. Byłem w nim na ćwiczenia.
 – 
wvoq
20 sierpień 2011, 21:14

3 odpowiedzi

Najlepsza odpowiedź

Cóż, w zasadzie robi to, że analizuje formy dostarczone jako jego ciało. Na przykład:

(defmacro listc (expr &rest forms)
  ;; 
  ;;
  ;; (listc EXP for VAR in GENERATOR [if CONDITION])
  ;;
  ;;
  (labels ((keyword-p (thing name)
             (and (symbolp thing)
                  (string= name thing))))
    (destructuring-bind (for* variable in* generator &rest tail) forms
      (unless (and (keyword-p for* "FOR") (keyword-p in* "IN"))
        (error "malformed comprehension"))
      (let ((guard (if (null tail) 't
                       (destructuring-bind (if* condition) tail
                         (unless (keyword-p if* "IF") (error "malformed comprehension"))
                         condition))))
        `(loop 
            :for ,variable :in ,generator 
            :when ,guard 
            :collecting ,expr)))))


(defun range (start end &optional (by 1))
  (loop
     :for k :upfrom start :below end :by by
     :collecting k))

Oprócz hackowego „parsera”, którego użyłem, rozwiązanie to ma wadę, która nie jest łatwa do rozwiązania w zwykłym sepleniu, a mianowicie konstrukcja list pośrednich, jeśli chcesz powiązać swoje rozumienia:

(listc x for x in (listc ...) if (evenp x))

Ponieważ w powszechnym sepleniu nie ma moralnego odpowiednika yield, trudno jest stworzyć udogodnienia, które nie wymagają pełnej materializacji wyników pośrednich. Jednym ze sposobów wyjścia z tego może być zakodowanie wiedzy o możliwych formach „generujących” w ekspanderze listc, tak aby ekspander mógł zoptymalizować/wbudować generowanie sekwencji bazowej bez konieczności konstruowania całej listy pośredniej przy uruchomieniu -czas.

Innym sposobem może być wprowadzenie "listy leniwych" (link wskazuje na schemat , ponieważ w powszechnym sepleniu nie ma równoważnego obiektu — najpierw trzeba było to zbudować, choć nie jest to szczególnie trudne).

Ponadto zawsze możesz rzucić okiem na kod innych osób, w szczególności, jeśli próbują rozwiązać ten sam lub podobny problem, na przykład:

4
Dirk 20 sierpień 2011, 12:19

Makra to transformatory kodu.

Istnieje kilka sposobów implementacji składni makra:

  • destrukturyzacja

Common Lisp dostarcza listę argumentów makr, która zapewnia również formę destrukturyzacji. Gdy używane jest makro, forma źródłowa jest destrukturyzowana zgodnie z listą argumentów.

Ogranicza to wygląd składni makr, ale dla wielu zastosowań makra zapewnia wystarczającą ilość maszyn.

Zobacz Listy makro Lambda w Common Lisp.

  • rozbiór gramatyczny zdania

Common Lisp daje również makro dostęp do całego formularza wywołania makr. Makro jest wtedy odpowiedzialne za parsowanie formularza. Parser musi być dostarczony przez autora makra lub jest częścią implementacji makra wykonanej przez autora.

Przykładem może być makro INFIX:

(infix (2 + x) * (3 + sin (y)))

Implementacja makra musi zaimplementować parser infix i zwrócić wyrażenie prefiksowe:

(* (+ 2 x) (+ 3 (sin y)))
  • oparte na regułach

Niektóre Lispy zapewniają reguły składni, które są dopasowywane do formularza wywołania makr. W przypadku pasującej reguły składni odpowiedni transformator zostanie użyty do utworzenia nowego formularza źródłowego. Można to łatwo zaimplementować w Common Lisp, ale domyślnie nie jest to mechanizm dostarczany w Common Lisp.

Zobacz przypadek składni w programie Scheme.

pętla

Aby zaimplementować składnię podobną do LOOP, należy napisać parser, który jest wywoływany w makrze, aby przeanalizować wyrażenie źródłowe. Zauważ, że parser nie działa na tekście, ale na wewnętrznych danych Lisp.

W przeszłości (lata 70.) było to używane w Interlisp w tak zwanym „Conversational Lisp”, który jest składnią Lisp z bardziej naturalną powierzchnią języka. Iteracja była częścią tego, a pomysł iteracji przeniósł się następnie do innych Lispów (takich jak LOOP Maclispa, skąd został następnie przeniesiony do Common Lisp).

Zobacz plik PDF na temat „Conversational Lisp” autorstwa Warrena Teitelmanna z Lata 70.

Składnia makra LOOP jest nieco skomplikowana i nie jest łatwo dostrzec granice między poszczególnymi podzdaniami.

Zobacz rozszerzoną składnię LOOP w Common Lisp.

(loop for i from 0 when (oddp i) collect i)

Taki sam jak:

(loop
   for i from 0
   when (oddp i)
   collect i)

Jednym z problemów, jakie ma makro LOOP jest to, że symbole takie jak FOR, FROM, WHEN i COLLECT nie są takie same z pakietu "COMMON-LISP" (przestrzeń nazw ). Kiedy teraz używam LOOP w kodzie źródłowym przy użyciu innego pakietu (przestrzeni nazw), to doprowadzi to do nowych symboli w tej źródłowej przestrzeni nazw. Z tego powodu niektórzy lubią pisać:

(loop
   :for i :from 0
   :when (oddp i)
   :collect i)

W powyższym kodzie identyfikatory odpowiednich symboli LOOP znajdują się w przestrzeni nazw KEYWORD.

Aby ułatwić zarówno parsowanie, jak i czytanie, zaproponowano przywrócenie nawiasów. Przykład takiego użycia makra może wyglądać tak:

(iter (for i from 0) (when (oddp i) (collect i)))

Taki sam jak:

(iter
  (for i from 0)
  (when (oddp i)
    (collect i)))

W powyższej wersji łatwiej jest znaleźć podwyrażenia i je przemierzać.

Makro ITERATE dla Common Lisp wykorzystuje to podejście.

Ale w obu przykładach trzeba przeszukiwać kod źródłowy za pomocą niestandardowego kodu.

3
Rainer Joswig 21 sierpień 2011, 18:16

Aby trochę uzupełnić odpowiedź Dirka: Pisanie własnych makr jest całkowicie wykonalne i może być miłym ćwiczeniem. Istnieje jednak kilka udogodnień dla tego rodzaju rzeczy (choć w sepleno-idiomatyczny sposób) wysokiej jakości, takich jak

Loop jest bardzo ekspresyjny, ale ma składnię nie przypominającą reszty zwykłego seplenienia. Niektórym redaktorom to się nie podoba i będą mieć słabe wcięcia. Jednak pętla jest zdefiniowana w standardzie. Zwykle nie jest możliwe napisanie rozszerzeń do pętli.

Iteracja jest jeszcze bardziej ekspresyjna i ma znajomą składnię lispy. Nie wymaga to żadnych specjalnych reguł wcięć, więc wszystkie edytory, które prawidłowo wcinają lisp, również będą ładnie wykonywać wcięcia. Iteracja nie jest w standardzie, więc musisz ją zdobyć samodzielnie (użyj quicklisp).

Seria to framework do pracy nad sekwencjami. W większości przypadków szeregi pozwolą nie przechowywać wartości pośrednich.

2
20 sierpień 2011, 21:23
Wiem - jak wspomniałem powyżej, znam się na pętli. Ale nie wiedziałem, jak działa składnia pętli pod maską, stąd ćwiczenie.
 – 
wvoq
20 sierpień 2011, 21:22