Co sprawia, że ​​makra Lisp są tak wyjątkowe?

297

Czytając eseje Paula Grahama na temat języków programowania, można by pomyśleć, że makra Lisp to jedyna droga. Jako zajęty programista pracujący na innych platformach nie miałem przywileju używania makr Lisp. Jako ktoś, kto chce zrozumieć szum, wyjaśnij, dlaczego ta funkcja jest tak potężna.

Proszę również odnieść to do czegoś, co zrozumiałbym ze światów programowania Python, Java, C # lub C.

miętowy
źródło
2
Nawiasem mówiąc, istnieje procesor makr w stylu LISP dla języka C # o nazwie LeMP: ecsharp.net/lemp ... JavaScript ma również taki o nazwie Sweet.js: sweetjs.org
Qwertie
@Qwertie Czy sweetjs obecnie działa nawet?
fredrik.hjarner
Nie korzystałem z niego, ale ostatnie zatwierdzenie miało miejsce sześć miesięcy temu ... wystarczająco dobrze dla mnie!
Qwertie

Odpowiedzi:

308

Aby udzielić krótkiej odpowiedzi, makra są używane do definiowania rozszerzeń składni języka dla Common Lisp lub DSL (Domain Specific Languages). Te języki są osadzone bezpośrednio w istniejącym kodzie Lisp. Teraz DSL mogą mieć składnię podobną do Lisp (jak interpreter Prologa Petera Norviga dla Common Lisp) lub zupełnie inną (np. Infix Notation Math dla Clojure).

Oto bardziej konkretny przykład:
Python ma wbudowane w listę interpretacje list. Daje to prostą składnię dla typowego przypadku. Linia

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

zwraca listę zawierającą wszystkie liczby parzyste od 0 do 9. W Pythonie 1,5 dnia nie było takiej składni; użyłbyś czegoś takiego:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

Oba są funkcjonalnie równoważne. Przywołajmy nasze zawieszenie niewiary i udawajmy, że Lisp ma bardzo ograniczone makro pętli, które po prostu wykonuje iterację i nie ma łatwego sposobu na zrobienie odpowiednika wyrażeń listowych.

W Lisp możesz napisać następujące. Powinienem zauważyć, że ten wymyślony przykład został wybrany jako identyczny z kodem Python, a nie dobrym przykładem kodu Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

Zanim przejdę dalej, powinienem lepiej wyjaśnić, czym jest makro. Jest to transformacja wykonywana na kod po kodzie. To znaczy fragment kodu odczytywany przez interpretera (lub kompilatora), który przyjmuje kod jako argument, manipuluje i zwraca wynik, który jest następnie uruchamiany w miejscu.

Oczywiście to dużo pisania, a programiści są leniwi. Możemy więc zdefiniować DSL do wykonywania list. W rzeczywistości używamy już jednego makra (makro pętli).

Lisp definiuje kilka specjalnych form składni. Quote ( ') wskazuje, że następny token jest dosłowny. Quasi-cytat lub backtick ( `) oznacza, że ​​następny token jest dosłowny ze znakami ucieczki. Ucieczki są oznaczone przez przecinek. Dosłowność '(1 2 3)jest odpowiednikiem języka Python [1, 2, 3]. Możesz przypisać ją do innej zmiennej lub użyć w miejscu. Możesz myśleć o `(1 2 ,x)tym, że jest odpowiednikiem Pythona, [1, 2, x]gdzie xjest wcześniej zdefiniowaną zmienną. Ten zapis listy jest częścią magii, która przechodzi w makra. Druga część to czytnik Lisp, który inteligentnie zastępuje makra kodem, ale najlepiej to ilustruje poniżej:

Możemy więc zdefiniować makro o nazwie lcomp(skrót od listy). Jego składnia będzie dokładnie taka jak w pythonie, którego użyliśmy w przykładzie [x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Teraz możemy wykonać w wierszu poleceń:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Całkiem nieźle, co? Teraz to nie koniec. Masz mechanizm lub pędzel, jeśli chcesz. Możesz mieć dowolną składnię. Jak withskładnia Python lub C # . Lub składnia LINQ .NET. To właśnie przyciąga ludzi do Lisp - najwyższa elastyczność.

gte525u
źródło
51
+1 za wdrożenie zrozumienia listy w Lisp, bo dlaczego nie?
ckb
9
@ckb Właściwie LISP ma już makro listowych w bibliotece standardowej: (loop for x from 0 below 10 when (evenp x) collect x), więcej przykładów tutaj . Ale rzeczywiście, pętla jest „tylko makrem” (zresztą jakiś czas temu ponownie ją
wdrożyłem
8
Wiem, że jest to dość niezwiązane, ale zastanawiam się nad składnią i tym, jak działa parsowanie ... Powiedzmy, że w ten sposób nazywam lcomp (zmieniając element thirs z „for” na „azertyuiop”): (lcomp x azertyuiop x in ( zakres 10) jeśli (= (% x 2) 0)) czy makro nadal będzie działać zgodnie z oczekiwaniami? Czy też parametr „for” jest używany w pętli, więc musi być ciągiem „for” po wywołaniu?
dader
2
@dader Więc pętla jest w rzeczywistości innym makrem, a dla jest specjalnym symbolem używanym w tym makrze. Mógłbym łatwo zdefiniować makro bez makra pętli. Jednak gdybym tak zrobił, post byłby znacznie dłuższy. Makro pętli i cała jej składnia jest zdefiniowana w CLtL .
gte525u
3
Jedną z rzeczy, które mnie mylą z makrami innego języka, jest to, że ich makra są ograniczone składnią języka hosta. Czy makra Lispy mogą interpretować składnię inną niż Lispy. Mam na myśli wyobraź sobie tworzenie składni typu haskell (bez nawiasów) i interpretowanie jej za pomocą makr Lisp. Czy jest to możliwe i jakie są zalety / wady korzystania z makr w porównaniu z bezpośrednim korzystaniem z lexera i parsera?
CMCDragonkai
105

Obszerna debata na temat makro lisp znajduje się tutaj .

Interesujący podzbiór tego artykułu:

W większości języków programowania składnia jest złożona. Makra muszą rozebrać składnię programu, przeanalizować ją i ponownie złożyć. Nie mają dostępu do parsera programu, więc muszą polegać na heurystyce i najlepszych przypuszczeniach. Czasami ich analiza redukcji jest błędna, a potem się psują.

Ale Lisp jest inny. Lisp makra zrobić mieć dostęp do parsera, a to jest naprawdę prosty parser. Makro Lisp nie przekazuje łańcucha, ale przygotowany fragment kodu źródłowego w postaci listy, ponieważ źródłem programu Lisp nie jest łańcuch; to jest lista. A programy Lisp są naprawdę dobre w rozbieraniu list i łączeniu ich z powrotem. Robią to niezawodnie każdego dnia.

Oto rozszerzony przykład. Lisp ma makro o nazwie „setf”, które wykonuje przypisanie. Najprostsza forma setf to

  (setf x whatever)

która ustawia wartość symbolu „x” na wartość wyrażenia „cokolwiek”.

Lisp ma również listy; możesz użyć funkcji „car” i „cdr”, aby uzyskać odpowiednio pierwszy element listy lub resztę listy.

Co teraz, jeśli chcesz zastąpić pierwszy element listy nową wartością? Jest do tego standardowa funkcja i niewiarygodnie, jego nazwa jest jeszcze gorsza niż „samochód”. To jest „rplaca”. Ale nie musisz pamiętać „rplaca”, ponieważ możesz pisać

  (setf (car somelist) whatever)

ustawić samochód somelisty.

Naprawdę dzieje się tutaj to, że „setf” jest makrem. W czasie kompilacji analizuje swoje argumenty i widzi, że pierwszy ma formę (samochód COŚ). Mówi do siebie: „Och, programista próbuje ustawić coś w samochodzie. Funkcją, którą można do tego użyć, jest„ rplaca ”.” I po cichu przepisuje kod, aby:

  (rplaca somelist whatever)
VonC
źródło
5
setf jest dobrą ilustracją potęgi makr, dzięki za jej włączenie.
Joel
Podoba mi się wyróżnienie .. ponieważ źródło programu Lisp nie jest łańcuchem; to jest lista. ! Czy to główny powód, dla którego makro LISP jest lepsze niż większość innych z powodu nawiasów?
Student
@Student Przypuszczam, że tak: books.google.fr/… sugeruje, że masz rację.
VonC
55

Typowe makra Lisp zasadniczo rozszerzają „prymitywy składniowe” twojego kodu.

Na przykład w C konstrukcja przełącznika / sprawy działa tylko z typami całkowymi, a jeśli chcesz użyć jej dla liczb zmiennoprzecinkowych lub łańcuchów, pozostaje ci zagnieżdżone instrukcje if i wyraźne porównania. Nie ma również możliwości napisania makra C, aby wykonać zadanie za Ciebie.

Ale ponieważ makro lisp jest (zasadniczo) programem lisp, który pobiera fragmenty kodu jako dane wejściowe i zwraca kod w celu zastąpienia „wywołania” makra, można rozszerzyć repertuar „prymitywny” tak daleko, jak chcesz, zwykle kończąc z bardziej czytelnym programem.

Aby zrobić to samo w C, musisz napisać niestandardowy procesor wstępny, który zjada twoje początkowe (niezupełnie C) źródło i wyrzuca coś, co kompilator C może zrozumieć. Nie jest to zły sposób, ale niekoniecznie najłatwiejszy.

Vatine
źródło
41

Makra Lisp pozwalają ci zdecydować, kiedy (jeśli w ogóle) jakakolwiek część lub wyrażenie zostanie ocenione. Aby podać prosty przykład, pomyśl o C:

expr1 && expr2 && expr3 ...

Mówi to: oceń expr1, a jeśli to prawda, oceń expr2itp.

Teraz spróbuj przekształcić to &&w funkcję ... tak, nie możesz. Wywoływanie czegoś takiego:

and(expr1, expr2, expr3)

Oceni wszystkie trzy exprsprzed udzieleniem odpowiedzi niezależnie od tego, czy expr1był fałszywy!

Za pomocą makr lisp możesz kodować coś takiego:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

teraz masz funkcję &&, którą możesz wywołać jak funkcję, i nie będzie oceniać żadnych przekazywanych do niej form, chyba że wszystkie są prawdziwe.

Aby zobaczyć, jak to jest przydatne, kontrast:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

i:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Inne rzeczy, które możesz zrobić z makrami, to tworzenie nowych słów kluczowych i / lub mini-języków (sprawdź (loop ...)makro na przykład), integrowanie innych języków w lisp, na przykład, możesz napisać makro, które pozwoli ci powiedzieć coś takiego:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

I to nawet nie wchodzi w makra programu Reader .

Mam nadzieję że to pomoże.

dsm
źródło
Myślę, że powinno to być: „(zastosuj &&, @ exprs); to i może być źle!”
Svante,
1
@svante - z dwóch powodów: po pierwsze, && to makro, a nie funkcja; zastosuj działa tylko na funkcjach. po drugie, zastosuj, weź listę argumentów do przekazania, więc chcesz jeden z „(funcall fn, @ exprs)”, „(Apply fn (list, @ exprs)” lub „(Apply fn, @ exprs nil)”, a nie „(zastosuj fn, @ exprs)”.
Aaron
(and ...będzie oceniać wyrażenia, dopóki nie zostanie ocenione na fałsz, zwróć uwagę, że wystąpią skutki uboczne wygenerowane przez fałszywą ocenę, tylko kolejne wyrażenia zostaną pominięte.
ocodo
31

Nie sądzę, żebym kiedykolwiek widział makra Lisp wyjaśniane lepiej niż ten facet: http://www.defmacro.org/ramblings/lisp.html

Rayne
źródło
3
Zwłaszcza jeśli masz tło Java / XML.
sunsations 21.12.12
1
Cóż za przyjemność czytać to leżące na mojej kanapie w sobotnie popołudnie! Bardzo jasno napisane i zorganizowane.
Colin
10

Pomyśl o tym, co możesz zrobić w C lub C ++ za pomocą makr i szablonów. Są to bardzo przydatne narzędzia do zarządzania powtarzalnym kodem, ale są ograniczone w dość poważny sposób.

  • Ograniczona składnia makr / szablonów ogranicza ich użycie. Na przykład nie możesz napisać szablonu, który rozwija się do czegoś innego niż klasa lub funkcja. Makra i szablony nie mogą łatwo obsługiwać danych wewnętrznych.
  • Złożona, bardzo nieregularna składnia C i C ++ utrudnia pisanie bardzo ogólnych makr.

Makra Lisp i Lisp rozwiązują te problemy.

  • Makra Lisp są napisane w Lisp. Masz pełną moc Lisp do napisania makra.
  • Lisp ma bardzo regularną składnię.

Porozmawiaj z każdym, kto opanował C ++ i zapytaj go, ile czasu spędzili na nauce wszystkich szablonów, których potrzebują, aby wykonać metaprogramowanie szablonów. Lub wszystkie zwariowane sztuczki w (doskonałych) książkach, takich jak Modern C ++ Design , które wciąż są trudne do debugowania i (w praktyce) nieprzenośne między kompilatorami ze świata rzeczywistego, mimo że język jest standaryzowany przez dekadę. Wszystko to rozpływa się, jeśli język używany do metaprogramowania jest tym samym językiem, którego używasz do programowania!

Matt Curtis
źródło
11
Cóż, szczerze mówiąc , problem z metaprogramowaniem szablonów C ++ nie polega na tym, że język metaprogramowania jest inny , ale że jest okropny - nie został tak zaprojektowany, jak został wykryty w czymś, co miało być znacznie prostszą funkcjonalnością szablonu.
Brooks Moses
@Brooks Sure. Nowe funkcje nie zawsze są złe. Niestety w wolno poruszającym się języku kierowanym przez komitety trudno jest je naprawić, gdy są. Szkoda, że ​​współczesne użyteczne nowe funkcje C ++ zostały napisane w języku, którego niewielu potrafi nawet przeczytać, i istnieje ogromna przepaść między przeciętnym programistą a „wysokim kapłanem”.
Matt Curtis,
2
@downvoter: jeśli coś jest nie tak z moją odpowiedzią, proszę zostawić komentarz, abyśmy wszyscy mogli podzielić się wiedzą.
Matt Curtis,
10

Makro lisp pobiera fragment programu jako dane wejściowe. Ten fragment programu reprezentuje strukturę danych, którą można manipulować i przekształcać w dowolny sposób. Na koniec makro generuje kolejny fragment programu, który jest wykonywany w czasie wykonywania.

C # nie ma funkcji makr, jednak odpowiednikiem byłoby, gdyby kompilator przeanalizował kod w drzewie CodeDOM i przekazał go do metody, która przekształciła go w inną CodeDOM, która jest następnie kompilowana do IL.

Może to być wykorzystywane do realizacji „cukier” składni podobnie do for each-statement using-clause, LINQ select-expressions i tak dalej, jako makra, które przekształca kodu bazowego.

Jeśli Java ma makra, możesz zaimplementować składnię Linq w Javie, bez potrzeby zmiany języka podstawowego przez Sun.

Oto pseudo-kod dla tego, jak usingmogłoby wyglądać makro w stylu seplenienia w języku C # do implementacji :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }
JacquesB
źródło
Teraz, że faktycznie jest Lisp stylu makro procesor dla C #, chciałbym podkreślić, że dla makro usingbędzie wyglądać tak ;)
Qwertie
9

Ponieważ istniejące odpowiedzi dają dobre konkretne przykłady wyjaśniające, co osiągają makra i w jaki sposób, być może pomogłoby to zebrać niektóre przemyślenia na temat tego, dlaczego funkcja makro jest znaczącym zyskiem w stosunku do innych języków ; najpierw z tych odpowiedzi, potem świetna z innych źródeł:

... w C musiałbyś napisać niestandardowy procesor wstępny [który prawdopodobnie kwalifikowałby się jako wystarczająco skomplikowany program w języku C ] ...

- Vatine

Porozmawiaj z każdym, kto opanował C ++ i zapytaj go, ile czasu spędzili na nauce wszystkich szablonów, których potrzebują, aby wykonać metaprogramowanie szablonów [co wciąż nie jest tak potężne].

- Matt Curtis

... w Javie musisz włamać się za pomocą tkania kodu bajtowego, chociaż niektóre frameworki, takie jak AspectJ, pozwalają ci to robić przy użyciu innego podejścia, jest to zasadniczo włamanie.

- Miguel Ping

DOLIST jest podobny do foreach Perla lub Pythona. Java dodała podobny rodzaj konstrukcji pętli z „ulepszoną” pętlą w Javie 1.5, jako część JSR-201. Zwróć uwagę na różnicę w makrach. Programista Lisp, który zauważa wspólny wzorzec w swoim kodzie, może napisać makro, aby uzyskać abstrakcję tego wzorca na poziomie źródła. Programista Java, który zauważa ten sam wzór, musi przekonać Sun, że ta konkretna abstrakcja jest warta dodania do języka. Następnie Sun musi opublikować JSR i zwołać „grupę ekspertów” z branży, aby wszystko zlikwidować. Ten proces - według Sun - zajmuje średnio 18 miesięcy. Następnie autorzy kompilatorów muszą zaktualizować swoje kompilatory, aby obsługiwać nową funkcję. I nawet gdy ulubiony kompilator programisty Java obsługuje nową wersję Java, prawdopodobnie nadal nie mogą korzystać z nowej funkcji, dopóki nie będą w stanie złamać kompatybilności źródła ze starszymi wersjami Javy. Tak więc irytacja, którą programiści Common Lisp mogą rozwiązać w ciągu pięciu minut, nęka programistów Java od lat.

- Peter Seibel, w „Practical Common Lisp”

ZakW
źródło
8

Nie jestem pewien, czy mogę dodać trochę wglądu do wszystkich (doskonałych) postów, ale ...

Makra Lisp działają świetnie ze względu na naturę składni Lisp.

Lisp jest niezwykle regularnym językiem (myśl o wszystkim to lista ); makra pozwalają traktować dane i kod tak samo (parsowanie ciągów lub inne modyfikacje nie są potrzebne do modyfikowania wyrażeń lisp). Łączysz te dwie funkcje i masz bardzo czysty sposób na modyfikację kodu.

Edycja: Chciałem powiedzieć, że Lisp jest homoiczny , co oznacza, że ​​struktura danych dla programu lisp jest napisana w samym lisp.

W efekcie powstaje sposób tworzenia własnego generatora kodu na języku przy użyciu samego języka z całą jego mocą (np. W Javie musisz włamać się do kodu bajtowego, chociaż niektóre frameworki, takie jak AspectJ, pozwalają ci zrób to przy użyciu innego podejścia, zasadniczo jest to hack).

W praktyce z makrami budujesz swój własny mini-język na bazie seplenienia, bez potrzeby uczenia się dodatkowych języków lub narzędzi oraz z wykorzystaniem pełnej mocy samego języka.

Miguel Ping
źródło
1
Jest to wnikliwy komentarz, jednak pomysł, że „wszystko jest listą” może przestraszyć przybyszów. aby zrozumieć listę, musisz zrozumieć minusy, samochody, cdrs, komórki. Mówiąc dokładniej, Lisp składa się z S-expressionslist, a nie z list.
ribamar
6

Makra Lisp reprezentują wzorzec występujący w prawie każdym dużym projekcie programistycznym. W końcu w dużym programie masz pewną sekcję kodu, w której zdajesz sobie sprawę, że napisanie programu, który generuje kod źródłowy w postaci tekstu, byłoby łatwiejsze i mniej podatne na błędy.

W Pythonie obiekty mają dwie metody __repr__i __str__. __str__jest po prostu reprezentacją czytelną dla człowieka. __repr__zwraca reprezentację, która jest poprawnym kodem Pythona, to znaczy coś, co można wprowadzić do interpretera jako prawidłowy Python. W ten sposób możesz tworzyć małe fragmenty Pythona, które generują prawidłowy kod, który można wkleić w twoim źródle.

W Lisp cały proces został sformalizowany przez system makr. Jasne, że umożliwia tworzenie rozszerzeń składni i robienie różnego rodzaju fantazyjnych rzeczy, ale ich faktyczna użyteczność została podsumowana powyżej. Oczywiście pomaga to, że system makr Lisp pozwala na manipulowanie tymi „fragmentami” z pełną mocą całego języka.

dnolen
źródło
1
Twój pierwszy akapit jest bardzo jasny dla osoby postronnej Lisp, co jest ważne.
Wildcard
5

Krótko mówiąc, makra są transformacjami kodu. Pozwalają wprowadzić wiele nowych konstrukcji składni. Np. Rozważ LINQ w C #. W lisp istnieją podobne rozszerzenia języka, które są implementowane przez makra (np. Wbudowana konstrukcja pętli, iteracja). Makra znacznie zmniejszają duplikację kodu. Makra pozwalają na osadzanie «małych języków» (np. Tam, gdzie w c # / java można użyć xml do konfiguracji, w lisp to samo można osiągnąć z makrami). Makra mogą ukrywać trudności w korzystaniu z bibliotek.

Np. W seplenienie możesz pisać

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

i to ukrywa wszystkie elementy bazy danych (transakcje, prawidłowe zamykanie połączenia, pobieranie danych itp.), podczas gdy w C # wymaga to utworzenia SqlConnections, SqlCommands, dodania SqlParameters do SqlCommands, zapętlenia SqlDataReaders, poprawnego ich zamknięcia.

dmitry_vk
źródło
3

Chociaż wszystko to wyjaśnia makra, a nawet ma fajne przykłady, myślę, że kluczową różnicą między makrem a normalną funkcją jest to, że LISP najpierw ocenia wszystkie parametry przed wywołaniem funkcji. W przypadku makra jest odwrotnie, LISP przekazuje parametry bez oceny do makra. Na przykład, jeśli przekażesz (+ 1 2) do funkcji, funkcja otrzyma wartość 3. Jeśli przekażesz to do makra, otrzyma Listę (+ 1 2). Można to wykorzystać do wykonywania wszelkiego rodzaju niezwykle przydatnych rzeczy.

  • Dodanie nowej struktury kontrolnej, np. Pętli lub dekonstrukcji listy
  • Zmierz czas potrzebny do wykonania przekazanej funkcji. W przypadku funkcji parametr zostanie oceniony przed przekazaniem kontroli do funkcji. Za pomocą makra możesz podzielić swój kod na początek i koniec stopera. Poniżej znajduje się dokładnie ten sam kod w makrze i funkcji, a wynik jest bardzo różny. Uwaga: Jest to wymyślony przykład, a implementacja została wybrana tak, aby była identyczna, aby lepiej podkreślić różnicę.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0
Joerg Schmuecker
źródło
Btw, Scala dodała makra do języka. Chociaż brakuje im piękna makr Lisp, ponieważ język nie jest homoikoniczny, zdecydowanie warto się nim przyjrzeć, a abstrakcyjne drzewa składniowe, które dostarczają, mogą być łatwiejsze w użyciu. Jest za wcześnie, aby powiedzieć, który system makro preferuję.
Joerg Schmuecker
2
„LISP przekazuje parametry nieocenione do makra” w końcu odpowiedź, która mówi wprost. ale zapomniałeś drugiej połowy tego: a wynikiem makra jest przekształcony kod, który zostanie oceniony w całości przez system zamiast oryginału, tak jakby był na pierwszym miejscu (chyba że sam jest ponownie wywołaniem makro, które będą również uzyskać zwerbowany przez tego makra tego czasu).
Czy Ness
0

Mam to z książki kucharskiej Lisp, ale myślę, że to wyjaśnia, dlaczego makra lisp są dobre w dobry sposób.

„Makro jest zwykłym fragmentem kodu Lisp, który działa na innym domniemanym kodzie Lisp, tłumacząc go na wersję wykonywalną Lisp. Może to zabrzmieć nieco skomplikowanie, więc dajmy prosty przykład. Załóżmy, że chcesz wersja setq, która ustawia dwie zmienne na tę samą wartość. Więc jeśli napiszesz

(setq2 x y (+ z 3))

gdy z=8zarówno x, jak i są ustawione na 11. (Nie mogę wymyślić żadnego zastosowania do tego, ale to tylko przykład.)

Powinno być oczywiste, że nie możemy zdefiniować setq2 jako funkcji. Jeśli x=50i y=-5, funkcja otrzyma wartości 50, -5 i 11; nie wiedziałby, jakie zmienne miały być ustawione. To, co naprawdę chcemy powiedzieć, to to, że kiedy widzisz (system Lisp) (setq2 v1 v2 e), potraktuj to jako równoważne (progn (setq v1 e) (setq v2 e)). W rzeczywistości nie jest to w porządku, ale na razie wystarczy. Makro pozwala nam to zrobić dokładnie, określając program do przekształcania wzorca wejściowego (setq2 v1 v2 e)„na wzorzec wyjściowy (progn ...)”.

Jeśli uważasz, że to było miłe, możesz dalej czytać tutaj: http://cl-cookbook.sourceforge.net/macros.html

nilsi
źródło
1
Można zdefiniować setq2jako funkcję, jeśli xi ysą przekazywane przez odwołanie. Nie wiem jednak, czy jest to możliwe w CL. Więc dla kogoś, kto nie zna Lispsa lub CL w szczególności, nie jest to bardzo ilustrujący przykład IMO
neoascetic
Przekazywanie argumentów @neoascetic CL ma wyłącznie wartość (dlatego właśnie potrzebuje makr). niektóre wartości są jednak wskaźnikami (jak listy).
Czy Ness
-5

W Pythonie masz dekoratory, w zasadzie masz funkcję, która przyjmuje inną funkcję jako dane wejściowe. Możesz robić, co tylko chcesz: wywołać funkcję, zrobić coś innego, zawinąć wywołanie funkcji w wersję pozyskiwania zasobów itp., Ale nie można zajrzeć do tej funkcji. Powiedzmy, że chcieliśmy zwiększyć jego moc, powiedzmy, że twój dekorator otrzymał kod funkcji jako listę, wtedy możesz nie tylko wykonać funkcję taką, jaka jest, ale możesz teraz wykonać jej części, zmienić kolejność wierszy funkcji itp.

kamieniste
źródło