2015-05-23 36 views
6

* Uwaga: pomimo częstego korzystania z StackOverflow przez długi czas, jest to pierwsze pytanie, które sam napisałem. Przepraszam, jeśli jest trochę gadatliwy. Konstruktywna krytyka doceniona.Makro, które definiuje funkcje, których nazwy są oparte na argumentach makra.

Kiedy definiuję strukturę w Common Lisp przy użyciu funkcji defstruct, automatycznie generowana jest funkcja predykatu, która sprawdza, czy jej argument jest typu zdefiniowanego przez defstruct. Np:

(defstruct book 
    title 
    author) 

(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain"))) 
    (book-p huck-finn)) 
=> True 

Jednak przy określaniu klasy przy użyciu defclass takie funkcje są pozornie nie generowane domyślnie (czy jest jakiś sposób, aby to określić?), Więc staram się dodać tę funkcjonalność siebie, bo d) a) aby składnia ta była zgodna między strukturami i klasami, b) mieć skrót (typep obj 'classname), który muszę napisać bardzo często i jest hałaśliwy wizualnie, i c) jako ćwiczenie programistyczne, ponieważ jestem wciąż stosunkowo nowe w Lisp.

mógłbym napisać makro, które definiuje funkcję predykatu daną nazwą klasy:

(defclass book() 
    ((title :initarg :title 
      :accessor title) 
    (author :initarg :author 
      :accessor author))) 

;This... 
(defmacro gen-predicate (classname) 
    ...) 

;...should expand to this... 
(defun book-p (obj) 
    (typep obj 'book)) 

;...when called like this: 
(gen-predicate 'book) 

imię, które trzeba przekazać do defun musi mieć postać 'classname-p. Tutaj mam trudności. Aby stworzyć taki symbol, mógłbym użyć funkcji "symb" z książki On Lisp Paula Grahama (s. Gdy jest prowadzony na REPL:

(symb 'book '-p) 
=> BOOK-P 

moich gen-źródłowych wygląd makro-, jak to do tej pory:

(defmacro gen-predicate (classname) 
    `(defun ,(symb classname '-p) (obj) 
    (typep obj ,classname))) 

(macroexpand `(gen-predicate 'book)) 
=> 
(PROGN 
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN '|'BOOK-P| 'NIL T)) 
(SB-IMPL::%DEFUN '|'BOOK-P| 
        (SB-INT:NAMED-LAMBDA |'BOOK-P| 
         (OBJ) 
        (BLOCK |'BOOK-P| (TYPEP OBJ 'BOOK))) 
        NIL 'NIL (SB-C:SOURCE-LOCATION))) 
T 

Wydawałoby się, że symbol stworzony przez (symb 'book '-p) jest faktycznie uznane |'BOOK-P| przez wdrożenie (SBCL), a nie BOOK-P. Rzeczywiście, to teraz działa:

(let ((huck-finn (make-instance 'book))) 
    (|'BOOK-P| huck-finn)) 
=> True 

Dlaczego symbol stworzony przez symb internowany jako |'BOOK-P|? W On Lisp (ta sama strona co powyżej) Graham mówi: "Dowolny ciąg znaków może być nazwą-print symbolu, nawet łańcuchem zawierającym małe litery lub znaki makr takie jak nawiasy. Jeśli nazwa symbolu zawiera takie osobliwości, jest drukowana w pionie bary. " W tym przypadku nie ma takich dziwactw, prawda? Czy mam rację, myśląc, że "nazwa-wydruku" symbolu jest tym, co jest faktycznie wyświetlane na standardowym wyjściu, gdy drukowany jest symbol, i czy w przypadku takich osobliwości różni się od postaci samego symbolu?

Aby móc pisać makra definiujące funkcje, takie jak gen-predicate - których zdefiniowane funkcje są nazwane na podstawie argumentów przekazanych do makra - wydaje mi się, że prawdopodobnie hackerzy Lisp prawdopodobnie robili od wieków. Użytkownik Kaz mówi tutaj (Merging symbols in common lisp), że często można uniknąć "zlepiania" symboli, ale to byłoby sprzeczne z celem tego makra.

Wreszcie, zakładając, że mogę uzyskać gen-predicate, aby działał tak, jak chcę, jaki byłby najlepszy sposób zagwarantowania, że ​​zostanie on nazwany dla każdej nowej klasy, ponieważ są one zdefiniowane? Podobnie jak initialize-instance można dostosować w celu wykonywania określonych czynności na klasie, czy istnieje ogólna funkcja wywoływana przez klasę deflacji, która może wykonywać akcje na klasie klasy ?

Dziękuję.

Odpowiedz

5

To zwykły problem: co zostaje przekazane do makra?

Porównaj połączeń tak:

(symb 'book '-p) 

i

(symb ''book '-p) 

makra forma to:

(gen-predicate 'book) 

GEN-PREDICATE jest makro. classname jest parametrem dla tego makra.

Jaka jest wartość classname wewnątrz makra podczas ekspansji kodu? Czy to jest book lub 'book?

W rzeczywistości jest to drugie, ponieważ napisałeś (gen-predicate 'book). Zapamiętaj: makra zobaczą kod źródłowy, a źródło argumentu zostanie przekazane do funkcji makra - nie wartość. Argumentem jest 'book. Tak więc to minęło. (QUOTE BOOK) jest taki sam, tylko drukowany inaczej. Jest to lista dwuelementowa. Pierwszym elementem jest symbol QUOTE, a drugim elementem jest symbol BOOK.

Tym samym makro wywołuje teraz funkcję SYMB z wartością argumentu (QUOTE BOOK) lub krótszą, 'BOOK.

Jeśli chcesz wygenerować orzecznik bez znaku cytując, trzeba napisać:

(gen-predicate book) 

Alternatywnie można również zmienić makro:

(symb classname '-p) 

byłoby:

(symbol (if (and (consp classname) 
       (eq (first classname) 'quote)) 
      (second classname) 
      classname)) 

Porównaj

Piszemy

(defun foo() 'bar) 

i nie

(defun 'foo() 'bar) ; note the quoted FOO 

DEFUN to makro i pierwszym argumentem jest nazwa funkcji. Jest to podobny problem to ...

Druga część pytania

ja naprawdę nie wiem, jakieś dobre odpowiedzi. Nie pamiętam żadnego łatwego sposobu uruchomienia kodu (na przykład do zdefiniowania funkcji) po definicji klasy.

  • Może użyć MOP, ale to jest brzydkie.

  • Napisz niestandardowe makro DEFINE-CLASS, które robi, co chcesz: rozwija się do DEFCLASS i DEFUN.

  • iteracyjne nad wszystkich symboli w pakiecie znaleźć klas i zdefiniuje odpowiednie orzeczników

+0

Oczywiście, genialny! Jakoś nie uważałem, że '' książka' jest naprawdę '(quote book)'. Rozumiem twoje drugie rozwiązanie, ale pierwsze jest mylące. W istocie zdałem sobie już sprawę, że (macroexpand '(książka predykatów genów) rozwija się w to, co chciałem, ale ponieważ symbol' book' nie jest cytowany, to jest interpretowany jako zmienna, która jest niezdefiniowana. Aby uniknąć tego problemu, dziękuję za szybką i pomocną odpowiedź! –

+0

Ta odpowiedź dobrze się sprawdza w pierwszej części pytania: jest też druga część, z której chciałbym się zapoznać: jak uzyskać to, aby zawsze było wywoływane, gdy tworzone są nowe klasy? (I/lub kiedy chciałoby się lub nie chciałoby się tego dokonać?) – lindes

1

Aby odpowiedzieć na drugą część pytania, zajęcia są same obiekty, dzięki MOP, więc może być możliwe napisanie: po metodzie na initialize-instance specjalizującej się w STANDARD-CLASS. Ale powinieneś sprawdzić MOP, aby sprawdzić, czy zdefiniowanie takiej metody jest dozwolone, czy nie.

Jeśli jest to możliwe, to tak, możesz uruchomić kod w odpowiedzi na stworzenie klasy; Jednak ponieważ nie znasz nazwy klasy tworzonej do czasu wykonania, nie możesz jej przeliterować tekstowo w źródle, więc nie możesz używać swojego makra (chyba że używasz eval). wolisz używać coś jak

(let ((classname (class-name class))) 
    (compile (generate-my-predicate-symbol classname) 
    (lambda (x) (typep x classname)))) 

myślę sugestia Rainera napisać własną DEF-CLASS makro jest do zrobienia, to znaczy drogą wytrawnym Lisper najprawdopodobniej będzie to zrobić, jeśli nie aren” t inne rozważania w grze. Ale nie jestem naprawdę doświadczonym Lisperem, więc mogę się mylić;)