2012-07-21 8 views
9

W moim dążeniu do pełnego zrozumienia tak potężnych makr lispów przyszło mi do głowy pytanie. Wiem, że złotą zasadą dotyczącą makr jest ta, która mówi: "Nigdy nie używaj makra, gdy funkcja wykona pracę". Jednak czytając rozdział 9 - - z książki Practical Common Lisp zostałem wprowadzony do poniższego makra, którego celem było pozbyć się powielania wyrażenia przypadku testowego, z towarzyszącym mu ryzykiem błędnego oznakowania wyników.Lisp: Makra kontra funkcje

;; Function defintion. 

(defun report-result (result form) 
    (format t "~:[FAIL~;pass~] ... ~a~%" result form)) 

;; Macro Definition 

(defmacro check (form) 
    `(report-result ,form ',form)) 

OK, rozumiem swój cel, ale mogę to zrobić za pomocą funkcji zamiast makra, na przykład:

(setf unevaluated.form '(= 2 (+ 2 3))) 

(defun my-func (unevaluated.form) 
    (report-result (eval unevaluated.form) unevaluated.form)) 
  1. Jest to możliwe tylko dlatego, że dana makro jest zbyt proste ?
  2. Co więcej, czy Lisp Macro System jest tak potężny, względnie jego przeciwnicy ze względu na sam kod - jak struktury kontrolne, funkcje, itp. - jest reprezentowany jako LISTA?
+6

Ta "złota reguła" jest głupia. Używaj makr wszędzie tam, gdzie uważasz, że są odpowiednie i zapomnij o wszystkich "regułach" uszkodzonych przez mózg. Co do Twojego przykładu, nie jest to prawie odpowiednik, ponieważ odkładasz kompilację do środowiska wykonawczego. Jeśli Twój formularz zawiera odniesienia do niektórych nazw o zasięgu lokalnym, po prostu nie zadziała. –

+0

Sk, czy mógłbyś podać przykład dotyczący funkcji eval i nazwy o zasięgu lokalnym? – utxeee

+3

'(niech ((x 2)) (eval '(+ x x)))' po prostu nie działałoby, OTOH gdyby ta forma '(+ x x)' została wygenerowana przez makro, byłoby naturalnie skompilowane. –

Odpowiedz

4

Lepszy substytucja nie byłoby z eval, które nie będą działać prawidłowo we wszystkich przypadkach (na przykład, nie ma dostępu do środowiska leksykalnego), a także jest przesadą (patrz tutaj: https://stackoverflow.com/a/2571549/977052), ale coś za pomocą anonimowych funkcje, jak poniżej:

(defun check (fn) 
    (report-result (funcall fn) (function-body fn))) 

CL-USER> (check (lambda() (= 2 (+ 2 3)))) 

Nawiasem mówiąc, jest to, jak takie rzeczy są realizowane w Ruby (funkcje anonimowe nazywane są procs tam).

Ale, jak widzisz, staje się nieco mniej elegancki (chyba że dodasz cukier składniowy) i, faktycznie, jest większy problem: nie ma funkcji function-body w Lisp (chociaż mogą istnieć niestandardowe sposoby na uzyskanie tego) . Ogólnie rzecz biorąc, jak widzisz, dla tego konkretnego zadania alternatywne rozwiązania są znacznie gorsze, chociaż w niektórych przypadkach takie podejście mogłoby zadziałać.

Ogólnie rzecz biorąc, jeśli chcesz coś zrobić z kodem źródłowym wyrażeń przekazywanych do makra (i zazwyczaj jest to główny powód używania makr), funkcje nie będą wystarczające.

12

Ale jeśli to była makro was mógłby zrobić:

(check (= 2 (+ 2 3))) 

Dzięki funkcji, trzeba zrobić:

(check '(= 2 (+ 2 3))) 

Również z Macro (= 2 (+ 2 3)) jest faktycznie skompilowany przez kompilator, podczas gdy funkcja jest oceniana przez funkcję eval, niekoniecznie to samo.

Addenda:

Tak, to tylko oceny funkcji. Co to oznacza, zależy od implementacji. Niektórzy mogą ją zinterpretować, inni mogą ją skompilować i wykonać. Ale prostą sprawą jest to, że nie wiesz od systemu do systemu.

Nieumiejętne środowisko leksykalne, o którym wspominali inni, to także wielka sprawa.

Rozważmy:

(defun add3f (form) 
    (eval `(+ 3 ,form))) 

(demacro add3m (form) 
    `(+ 3 ,form)) 

Następnie obserwować:

[28]> (add3m (+ 2 3)) 
8 
[29]> (add3f '(+ 2 3)) 
8 
[30]> (let ((x 2)) (add3m (+ x 3))) 
8 
[31]> (let ((x 2)) (add3f '(+ x 3))) 

*** - EVAL: variable X has no value 
The following restarts are available: 
USE-VALUE  :R1  Input a value to be used instead of X. 
STORE-VALUE :R2  Input a new value for X. 
ABORT   :R3  Abort main loop 
Break 1 [32]> :a 

To naprawdę bardzo obciążające dla większości przypadków użycia. Ponieważ eval nie ma środowiska leksykalnego, nie może "zobaczyć" x z otaczającego go let.

+0

Czy używam tutaj funkcji eval po prostu oceniam wyrażenie? – utxeee

+1

@uxtee: Domyślnie 'eval' działa w pustym środowisku leksykalnym. To prawie nigdy nie jest to, czego chcesz. – Vatine

3

Funkcja report-result wymaga zarówno kodu źródłowego, jak i wyniku wykonania.

Makro CHECK udostępnia zarówno z pojedynczego formularza źródłowego.

Jeśli umieścisz w pliku kilka formularzy check, zostaną one łatwo skompilowane przy użyciu zwykłego procesu kompilowania plików Lisp. Dostaniesz skompilowaną wersję kodu sprawdzającego.

Używanie funkcji i EVAL (lepiej użyć COMPILE) spowodowałoby odłożenie oceny źródła na później. Nie byłoby również jasne, czy jest interpretowane czy kompilowane. W przypadku kompilacji uzyskasz później kontrolę kompilatora.