Common Lisp pozwala pisać makra, które wykonują dowolną transformację źródłową.
Schemat zapewnia higieniczny system dopasowywania wzorów, który pozwala również przeprowadzać transformacje. Jak przydatne są makra w praktyce? Paul Graham powiedział w Beating the Averages, że:
Kod źródłowy edytora Viaweb zawierał prawdopodobnie około 20-25% makr.
Jakie rzeczy ludzie faktycznie robią z makrami?
Odpowiedzi:
Spójrz na ten post Matthiasa Felleisena na liście dyskusji LL1 w 2002 roku. Sugeruje trzy główne zastosowania makr:
źródło
Najczęściej używam makr do dodawania nowych, oszczędzających czas konstrukcji języka, które w innym przypadku wymagałyby dużej ilości kodu z podstawowymi informacjami.
Na przykład niedawno odkryłem, że chcę imperatywu
for-loop
podobnego do C ++ / Java. Ponieważ jednak był językiem funkcjonalnym, Clojure nie wyszedł z niego po wyjęciu z pudełka. Właśnie zaimplementowałem to jako makro:A teraz mogę zrobić:
I oto masz - nowa konstrukcja języka ogólnego do kompilacji w sześciu liniach kodu.
źródło
Pisanie rozszerzeń językowych lub DSL.
Aby to sprawdzić w językach podobnych do Lisp , zapoznaj się z Racket , który ma kilka wariantów językowych: Typed Racket, R6RS i Datalog.
Zobacz także język Boo, który daje dostęp do potoku kompilatora w konkretnym celu tworzenia języków specyficznych dla domeny za pomocą makr.
źródło
Oto kilka przykładów:
Schemat:
define
dla definicji funkcji. Zasadniczo jest to krótszy sposób definiowania funkcji.let
do tworzenia zmiennych o zasięgu leksykalnym.Clojure:
defn
, zgodnie z jego dokumentami:Taki sam jak (def name (fn [params *] exprs *)) lub (def name (fn ([params *] exprs *) +)) z dowolnym łańcuchem doc lub atrybutami dodanymi do metadanych var
for
: lista wyrażeńdefmacro
: ironiczny?defmethod
,defmulti
: praca z wieloma metodamins
Wiele z tych makr znacznie ułatwia pisanie kodu na bardziej abstrakcyjnym poziomie. Myślę, że makra są pod wieloma względami podobne do składni w wersjach innych niż Lisps.
Biblioteka drukowania Incanter zapewnia makra dla niektórych złożonych manipulacji danymi.
źródło
Makra są przydatne do osadzania niektórych wzorców.
Na przykład Common Lisp nie definiuje
while
pętli, ale ma to,do
czego można użyć do jej zdefiniowania.Oto przykład z On Lisp .
Spowoduje to wydrukowanie „12345678910”, a jeśli spróbujesz zobaczyć, co się stanie z
macroexpand-1
:Zwróci to:
To proste makro, ale jak powiedziano wcześniej, są one zwykle używane do definiowania nowych języków lub DSL, ale z tego prostego przykładu możesz już spróbować wyobrazić sobie, co możesz z nimi zrobić.
loop
Makro jest dobrym przykładem tego, co może zrobić makra.Common Lisp ma inny rodzaj makr zwanych makrami czytników, których można używać do modyfikowania sposobu interpretacji kodu przez czytnika, tzn. Można ich używać do # # i #} ma ograniczniki takie jak # (i #).
źródło
Oto jeden, którego używam do debugowania (w Clojure):
Musiałem poradzić sobie z ręcznie zwijaną tabelą skrótów w C ++, w której
get
metoda przyjęła jako argument ciąg non-const, co oznacza, że nie mogę tego nazwać dosłownie. Aby łatwiej sobie z tym poradzić, napisałem coś takiego:Podczas gdy coś podobnego do tego problemu raczej nie pojawi się w lisp, uważam za szczególnie miłe, że możesz mieć makra, które nie oceniają ich argumentów dwa razy, na przykład poprzez wprowadzenie prawdziwego let-bind. (Przyznaję, że mogłem to obejść).
Uciekam się też do okropnie brzydkiego sposobu pakowania rzeczy w
do ... while (false)
taki sposób, że można go użyć w części „jeśli” i nadal wykonywać inne czynności zgodnie z oczekiwaniami. Nie potrzebujesz tego w lisp, który jest funkcją makr działających na drzewach składniowych, a nie na ciągach (lub sekwencjach tokenów, jak sądzę w przypadku C i C ++), które następnie są analizowane.Istnieje kilka wbudowanych makr wątków, których można użyć w celu reorganizacji kodu, tak aby czytał on bardziej czytelnie („wątkowanie” jak w „zasiewie kodu razem”, a nie równoległości). Na przykład:
Przyjmuje pierwszą formę
(range 6)
i czyni ją ostatnim argumentem następnej formy,(filter even?)
która z kolei jest ostatnim argumentem następnej formy i tak dalej, tak że powyższe zostaje przepisane naMyślę, że pierwszy z nich brzmi znacznie jaśniej: „weź te dane, zrób to, następnie zrób to, a potem zrób drugie i gotowe”, ale to subiektywne; obiektywnie prawda polega na tym, że odczytujesz operacje w kolejności, w jakiej są wykonywane (ignorując lenistwo).
Istnieje również wariant, który wstawia poprzednią formę jako pierwszy (a nie ostatni) argument. Jeden przypadek użycia jest arytmetyczny:
Odczytuje jako „weź 17, odejmij 2 i podziel przez 3”.
Mówiąc o arytmetyki, możesz napisać makro, które analizuje zapis notacji poprawkowej, abyś mógł powiedzieć np.
(infix (17 - 2) / 3)
I wyplułoby to,(/ (- 17 2) 3)
co ma tę wadę, że jest mniej czytelne i ma tę zaletę, że jest prawidłowym wyrażeniem lisp. To jest część językowa DSL / danych.źródło