2012-02-23 6 views
9

Na poziomie koncepcyjnym makro w LISP (i dialektach) pobiera fragment kodu (jako listę) i zwraca kolejny fragment kodu (ponownie jako listę).Różnica między używaną listą a tyłem w makrach

Na podstawie powyższej zasady proste makro mogą być:

(defmacro zz [a] (list print a)) 
;macroexpand says : (#<core$print [email protected]> "Hello") 

Ale w Clojure tym można również zapisać jako:

(defmacro zz [a] `(print ~a)) 
;macroexpand says : (clojure.core/print "Hello") 

Nie jestem dokładnie pewien temat różnicy tutaj, a które powinny być preferowanym sposobem. Pierwszy z nich wygląda na prosty, ponieważ powinniśmy zwrócić listę i unikać używania dziwnych znaków, takich jak tyknięcie z powrotem.

Odpowiedz

8

Nikt nie wskazywał na to jeszcze ... różnica między makrami 2 to: twoja druga forma (stosując lewy apostrof)

(defmacro zz [a] `(print ~a)) 

odpowiada:

(defmacro zz [a] (list 'print a)) 

który różni się od pierwszego przykładu:

(defmacro zz [a] (list print a)) 

Zwróć uwagę na brakujący pojedynczy cytat - dlatego Twoje makroekspozycja jest inna. Zgadzam się z innymi osobami publikującymi: użycie backquote jest bardziej konwencjonalne, jeśli twoje makro ma dość prosty "kształt". Jeśli musisz wykonać chodzenie po kodzie lub dynamiczną konstrukcję (czyli złożone makro), to często używasz list i budujesz to, co zostało zrobione.

Mam nadzieję, że to wyjaśnienie ma sens.

+1

Jest to właściwie odpowiednik '(list \' print a) ', który jest nieco inny:' 'print' będzie działał tylko wtedy, gdy wywołujący ma 'print' odwołujący się do' clojure.core/print' zamiast lokalnego, lub nie ma żadnego wiązania (powiedzmy, że wykluczyli je z ': refer-clojure' ich przestrzeni nazw). Z kolei '' 'print' rozwija się bezpośrednio na' clojure.core/print', więc jest niewiarygodny i poprawny w każdym kontekście. – amalloy

+0

@amalloy Dzięki za odpowiedź, na podstawie Twojej sugestii po prostu próbowałem zrobić makroexpand (przez szlam) na '' '(print ~ a)' i wróciłem: '(seq (concat (list 'print) (lista a))) '- ah, ale mówisz, że pre-transformowana forma jest bardziej podobna do' '' print', czyż nie? –

+0

Nie mogę odczytać twojej odpowiedzi bardzo dobrze, ponieważ SO je niektóre postacie, ale: Slime ukrywa przestrzeń nazw od ciebie (i makroexpand nie jest konieczne). Jeśli tylko zacytujesz wyrażenie, wpisując '' \ '(print ~ a) 'w repl, zobaczysz, że jest to odpowiednik' (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/print)) (clojure.core/list a))) '. 'Clojure.core/print' to ważne rozróżnienie, które robiłem. – amalloy

1

Z mojego doświadczenia wynika, że ​​są one równoważne. Chociaż mogą istnieć pewne skrajne przypadki, których nie jestem świadomy.

przykład @islon „s można równoważnie zapisać jako:

W przeciwieństwie do tego, powyższy kod można równoważnie być napisane tak:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
5

Jest różnica między nimi styl. Twój przykład jest bardzo prosty, ale w bardziej złożonych makrach różnica będzie większa.

Na przykład o ile makro, jak określono w "The Joy of Clojure" książka:

(defmacro unless [condition & body] 
    `(if (not ~condition) 
     (do [email protected]))) 

Z książki:

Składnia-cytat pozwala następujące IF-formularz, aby działać jako rodzaj szablonu dla wyrażenia , że dowolne użycie makra staje się po jego rozwinięciu.

Podczas tworzenia makra zawsze wybieraj najbardziej czytelny i idiomatyczny styl.

W przeciwieństwie do tego, powyższy kod można równoważnie być napisane tak:

(defmacro unless [condition & body] 
    (list 'if (list 'not condition) 
      (list* 'do body))) 
6

Tworzenie list jawnie jest "najprostsze", w pewnym sensie, ponieważ istnieje kilka podstawowych pojęć, które musisz znać: po prostu zaakceptuj listę i zmień ją, dopóki nie uzyskasz nowej listy. Backtick jest wygodnym skrótem do "szablonowania" fragmentów kodu; możliwe jest zapisanie dowolnego makra bez niego, ale dla każdego dużego makra szybko staje się bardzo nieprzyjemne. Na przykład, należy rozważyć dwa sposoby pisania let jako makro ponad fn:

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    `((fn [[email protected]] 
     (do [email protected])) 
     [email protected]))) 

(defmacro let [bindings & body] 
    (let [names (take-nth 2 bindings) 
     vals (take-nth 2 (rest bindings))] 
    (cons (list `fn (vec names) (cons `do body)) 
      vals))) 

W pierwszym przypadku, przy użyciu znaku odwróconego apostrofu czyni to dość oczywiste, że piszesz funkcję nazw zawierających ciało, a następnie wywołanie to z wartościami - kod makro jest "ukształtowany" tak samo jak kod ekspansji, więc możesz sobie wyobrazić, jak będzie wyglądać.

W drugim przypadku, z tylko cons i list w każdym miejscu, to prawdziwy ból głowy, aby dowiedzieć się, jak będzie wyglądać rozszerzenie. Oczywiście nie zawsze tak jest: czasami łatwiej jest napisać coś bez backtick.

Innym bardzo ważnym punktem została wykonana przez Kyle Burton: print nie jest taka sama jak 'print! Twoja ekspansja makra powinna zawierać symbol symbolprint, a nie jego wartość (która jest funkcją). Osadzanie obiektów (takich jak funkcje) w kodzie jest bardzo delikatne i działa tylko przez przypadek. Upewnij się więc, że makra rozwijają się do kodu, który mógłbyś sam napisać, i niech system ewaluacyjny wykona ciężką pracę - możesz wpisać symbol print, ale nie możesz wpisać wskaźnika do bieżącej wartości funkcji print .

Powiązane problemy