2013-07-24 14 views
6

Kod Lisp/Clojure ma spójność w ich składni i jest plusem, ponieważ nie trzeba rozumieć różnych konstrukcji. Ale czasami Łatwiej jest zrozumieć, patrząc na kawałek kodu, po prostu przez inną składnię używaną jak to jest obudowa przełącznika lub to jest konstrukcja dopasowująca wzorce itp. Bez faktycznego czytania tekstu.Jak rozpoznać, które formularze są makrami, a jakie funkcje podczas przeglądania kodu Clojure?

Zacząłem z Clojure kilka miesięcy temu i zdałem sobie sprawę, że nie mogę zrozumieć kodu bez czytania nazwy formularza, a następnie googling, czy jest to makro lub funkcja i jak to działa.

Okazuje się, że fragment kodu Clojure, niezależnie od jednolitości składni, nie jest jednolity.

Może się wydawać, że jest funkcją, ale jeśli w ogóle jest makrem, może nie analizować wszystkich swoich argumentów.

Czy istnieje konwencja nazewnictwa lub styl wcięć, z których korzystają wszystkie makra, aby łatwiej było komuś pojąć nazwę, co się dzieje?

+1

Świetne pytanie. Nie sądzę, żeby można było zobaczyć różnicę tylko patrząc. Jako początkujący mówimy, że składnia jest prosta, wszystko jest (fn args), więc pytasz dlaczego nie (zastosować lub zbić)? A dlaczego nie (map quote coll)? Och, to makra (lub specjalne formy). Po prostu nauczysz się odgadywać, która jest która. – Hendekagon

+0

hehe: (fn? +) True (fn? ->) Nie można wziąć wartości makra! I nie ma makra? lub specjalny formularz? Ale, (: makro (meta # '->)) true – Hendekagon

+0

@ Hendekagon: To jest coś, czego uczy się z doświadczeniem, ale to nie jest po prostu * zgadywanie *, że się uczy, ale raczej * domyśla się * odgadywania * (często z (blisko) pewność). Biorąc na przykład '(map quote coll)', 'quote' nie może być funkcją z powodu fundamentalnych właściwości języka, a w każdym przypadku' (map quote coll) 'jest wywołaniem funkcji, które odbiera' coll' post ewaluacja, więc nie jest jasne, co jest zamierzone (chociaż '(map # (list 'quote' quote%) ...)' może być całkiem przydatne w definicji makra, o ile '...' jest kolekcją, której makro ma dostęp do). –

Odpowiedz

4

Najbardziej użyteczna intuicja, moim zdaniem, wynika ze zrozumienia celu danego operatora/Var. Dobrze zaprojektowane makra po prostu nie mogą być napisane jako funkcje i nadal oferują tę samą funkcjonalność z tą samą składnią, ponieważ gdyby mogły, byłyby napisane jako funkcje (patrz "dobrze zaprojektowana" część powyżej!). Tak więc, jeśli masz do czynienia z konstruktem, który nie może być normalną funkcją, to wiesz, że tak nie jest; inaczej jest prawdopodobne.

Ponadto, zwykłe sposoby poznawania VAR wyeksportowanych przez bibliotekę informują, czy masz do czynienia z makrem lub funkcją z góry. To prawda, że ​​doc ((doc foo) mówi, że foo jest makrem w górnej części jego wyjścia, jeśli tak jest rzeczywiście), source (ponieważ daje ci cały kod) i M-. (przejdź do definicji w Emacsie z nrepl.el lub swank-clojure; M-, przeskakuje z powrotem). Można oczekiwać, że dokumentacja będzie zawierała wzmiankę o makrze, a co nie (oprócz tego, że niekoniecznie jest to prawda w przypadku docstrukcji, ponieważ wszystkie zwykłe sposoby uzyskiwania dostępu do dokumentacji docstringa już informują, czy masz do czynienia z makrem, jak wyjaśniono powyżej).

Jeśli prześladujesz kodeks z zamiarem sformułowania przybliżonej wiedzy na temat tego, co prawdopodobnie robi przy założeniu, że różni operatorzy wykonują funkcje sugerowane przez ich nazwy, wtedy (1) nazwy są sugestywne wystarczy, abyś zorientował się, co jest zamierzone przez kod, więc nie musisz nawet przejmować się tym, którzy operatorzy są makrami, lub (2) nazwy nie są wystarczająco sugestywne, więc musisz zanurkować w dokumentach lub źródło dla niektórych operatorów, a następnie pierwszą rzeczą, którą się dowiadujesz, jest to, które z nich są zarejestrowane jako makra.

Wreszcie, nie ma jednego stylu nazewnictwa dla makr, chociaż istnieją pewne konwencje specyficzne dla konkretnych przypadków użycia. Na przykład konstrukcje w stylu with-foo wydają się być makrami wygody, których celem jest uproszczenie obsługi zasobów typu foo; dofoo-style to zazwyczaj makra, które pobierają wyrazy do wykonania (ile razy iz jakim dodatkowym kontekstem zależy od makra, najbardziej podstawowy członek tej rodziny, do, jest w rzeczywistości specjalną formą, a nie makro); deffoo Konstrukcje w stylu wprowadzają nowe warianty lub jednostki podobne do typu.

Warto zauważyć, że podobne wzory są czasami łamane. Na przykład, większość konstruktów gwintujących (-> & Co.) to makra, ale xml-> z clojure.data.zip.xml jest funkcją. Ma to sens, gdy weźmie się pod uwagę dostarczoną funkcjonalność, która doprowadza nas do punktu, w którym operator jest najbardziej użytecznym źródłem intuicji.


Mogą istnieć pewne wyjątki od tej reguły. Można oczekiwać, że zostaną udokumentowane. Niektóre projekty oczywiście nie są udokumentowane (lub bardzo blisko); tutaj problem znika całkowicie, ponieważ i tak trzeba iść do źródła, żeby zrozumieć sens rzeczy.

0
O ile mi wiadomo, nie ma wymuszonej konwencji nazewnictwa.

Zgodnie z ogólną zasadą preferowane są funkcje, gdziekolwiek jest to możliwe, ale makra mogą być czasami wykrywane, gdy podążają za wzorcem def<something>, aby skonfigurować coś lub with-<resource> do zrobienia czegoś z otwartym zasobem.

Z tego powodu pomocne może okazać się użycie narzędzia clojure's doc. Pokaże ci, czy formularz jest makrem/funkcją/specjalną formą, a także podaj jego listę argumentów i ciąg dokumentów (jeśli są obecne). Na przykład:

(use 'clojure.repl) 
(doc and) 

Wydrukuje następujące elementy do rep.

clojure.core i/

([], [x] [x & następne])

makro

ocenia exprs po jednym na raz, od lewej do prawej. Jeśli formularz zwraca logiczną wartość false (zero lub fałsz) i zwraca tę wartość, a nie ocenia żadnego z innych wyrażeń, w przeciwnym razie zwraca wartość z wartością ostatniego wyrażenia. (and) zwraca true.

Niektóre edytory (np. Emacs) dostarczają tę dokumentację jako wyskakujące okienko lub kombinację klawiszy, co znacznie ułatwia dostęp do niego (i czytanie).

+0

"Z tego powodu pomocne może okazać się makroekonomiczne narzędzie clojure, które powie, czy formularz jest makrem/funkcją/specjalną formą, a także podaje listę argumentów i ciąg dokumentów (jeśli są obecne)" To zdecydowanie pomocny. działa to jedno makro zdefiniowane przez samego programistę? –

+0

Tak. Kompilator wie, że powinien używać funkcji jako makra, ponieważ Var, w którym jest przechowywany, ma wartość ': doc true' zawartą w mapie metadanych. 'doc' wyszukuje ten sam fragment metadanych. –

1

Istnieją dwa atrybuty, które zwykle wyróżniają makra (lub czasami specjalnego formularza) z funkcją:

  1. Gdy postać ma jakiś wiążące (tj deklarowania nowych identyfikatorów do późniejszego wykorzystania)
  2. Kiedy niektóre argumenty są oceniano leniwie

Przykłady pierwszym przypadku są let, letfn, binding i with-local-vars. Dziwnie jednak, defn jest zdefiniowany jako funkcja, ale jestem prawie pewien, że ma to coś wspólnego z procesem ładowania Clojure (defn jest zdefiniowany przed zdefiniowaniem defmacro).

Przykładami drugiego są and, or i lazy-seq. We wszystkich tych konstrukcjach argumenty są leniwie oceniane przez umieszczenie ich w gałęziach warunkowych (takich jak if) lub przeniesienie ich wewnątrz ciała funkcji.

Obie te cechy są po prostu manifestacją makra manipulującą składnią Clojure. Nie sądzę, aby makra z wątkami (-> i ->>) pasowały bardzo dobrze do jednej z tych kategorii, ale wersje bezpieczne zerowe (-?> i -?>>) są objęte leniwymi argumentami

+1

'defn' jest makro; jest zarejestrowany jako taki przez '(. (var defn) (setMacro))' bezpośrednio po jego definicji. Powodem tego jest, jak wskazujesz - niedostępność 'defmacro' w tym momencie. W rzeczywistości 'defmacro' opakowuje ten wzorzec (' defn', po którym następuje wywołanie 'setMacro') z dodatkowym preprocessingiem (do wstrzykiwania niejawnych parametrów' & form' i '& env'). –

+0

@ MichałMarczyk - Dzięki za potwierdzenie tego. Definicja 'defn' wydaje się zwracać (cytowaną) składnię, więc wygląda jak makro, mimo że jest zdefiniowana przez' def' i 'fn'. Wydaje mi się, że ma to sens, ponieważ makra są po prostu funkcjami wywoływanymi w czasie kompilacji zamiast w czasie wykonywania. Dzięki za wskazanie części 'setMacro' też! – DaoWen

Powiązane problemy