Zdanie odrębne: homoikoniczność Lisp jest o wiele mniej przydatna niż większość fanów Lisp chciałaby w to uwierzyć.
Aby zrozumieć makra składniowe, ważne jest, aby zrozumieć kompilatory. Kompilatorem jest przekształcanie kodu czytelnego dla człowieka w kod wykonywalny. Z bardzo wysokiego poziomu ma to dwie ogólne fazy: parsowanie i generowanie kodu .
Parsowanie to proces odczytu kodu, interpretowania go zgodnie z zestawem reguł formalnych i przekształcania go w strukturę drzewa, ogólnie znaną jako AST (abstrakcyjne drzewo składniowe). Mimo całej różnorodności języków programowania jest to jedna niezwykła wspólność: zasadniczo każdy język programowania ogólnego przeznaczenia analizuje strukturę drzewa.
Generowanie kodu bierze AST parsera jako dane wejściowe i przekształca go w kod wykonywalny poprzez zastosowanie formalnych reguł. Z punktu widzenia wydajności jest to znacznie prostsze zadanie; wiele kompilatorów języka wysokiego poziomu spędza 75% lub więcej czasu na analizie.
Należy pamiętać o Lisp, ponieważ jest bardzo, bardzo stary. Wśród języków programowania tylko FORTRAN jest starszy niż Lisp. Dawno temu parsowanie (powolna część kompilacji) było uważane za mroczną i tajemniczą sztukę. Oryginalne artykuły Johna McCarthy'ego na temat teorii Lispa (kiedy był to tylko pomysł, którego nigdy nie myślał, że można go zaimplementować jako prawdziwy język programowania komputerowego) opisują nieco bardziej złożoną i ekspresyjną składnię niż współczesne „wyrażenia S wszędzie na wszystko " notacja. Stało się to później, kiedy ludzie próbowali to wdrożyć. Ponieważ parsowanie nie było wówczas dobrze zrozumiałe, po prostu spreparowali go i wrzucili składnię do homoikonicznej struktury drzewa, aby zadanie parsera było całkowicie trywialne. Rezultat końcowy jest taki, że ty (programista) musisz wykonać dużo parsera ” s pracuj nad tym, pisząc formalny kod AST bezpośrednio w kodzie. Homoikoniczność „nie sprawia, że makra są o wiele łatwiejsze”, ponieważ sprawia, że pisanie wszystkiego innego jest o wiele trudniejsze!
Problem polega na tym, że szczególnie w przypadku dynamicznego pisania wyrażenia S bardzo trudno przenoszą ze sobą wiele informacji semantycznych. Gdy cała składnia jest tego samego typu (listy list), składnia nie ma zbyt wiele kontekstu, a więc system makr ma bardzo niewiele do pracy.
Teoria kompilatorów przeszła długą drogę od lat 60., kiedy wynaleziono Lisp, i chociaż rzeczy, które udało się osiągnąć, były imponujące jak na swój dzień, teraz wyglądają raczej prymitywnie. Na przykład nowoczesnego systemu metaprogramowania przyjrzyj się (niestety niedocenionemu) językowi Boo. Boo jest typem statycznym, obiektowym i otwartym, więc każdy węzeł AST ma typ o dobrze zdefiniowanej strukturze, do którego programista makr może odczytać kod. Język ma stosunkowo prostą składnię zainspirowaną Pythonem, z różnymi słowami kluczowymi, które nadają wewnętrzne znaczenie semantyczne zbudowanym z nich strukturom drzewa, a jego metaprogramowanie ma intuicyjną składnię quasi-cytatową, aby uprościć tworzenie nowych węzłów AST.
Oto makro, które utworzyłem wczoraj, kiedy zdałem sobie sprawę, że stosuję ten sam wzorzec do wielu różnych miejsc w kodzie GUI, gdzie wywoływałbym BeginUpdate()
formant interfejsu użytkownika, wykonał aktualizację w try
bloku, a następnie wywołał EndUpdate()
:
macro UIUpdate(value as Expression):
return [|
$value.BeginUpdate()
try:
$(UIUpdate.Body)
ensure:
$value.EndUpdate()
|]
macro
Polecenia jest, w rzeczywistości, samą w sobie makro , który zajmuje makro ciała, oraz generujący klasę przetwarzać makro. Używa nazwy makra jako zmiennej, która MacroStatement
zastępuje węzeł AST reprezentujący wywołanie makra. [| ... |] jest blokiem quasi-cytatu, generującym AST, który odpowiada kodowi wewnątrz, a wewnątrz bloku quasi-cytatu symbol $ zapewnia funkcję „quote-quote”, zastępując w węźle, jak określono.
Dzięki temu można napisać:
UIUpdate myComboBox:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
i rozwinąć do:
myComboBox.BeginUpdate()
try:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
ensure:
myComboBox.EndUpdate()
Wyrażenie makra w ten sposób jest prostsze i bardziej intuicyjne niż w makrze Lisp, ponieważ programista zna strukturę MacroStatement
i wie, jak działają Arguments
i Body
właściwości, a tę wiedzę można wykorzystać do wyrażenia pojęć związanych z bardzo intuicyjną droga. Jest to również bezpieczniejsze, ponieważ kompilator zna strukturę MacroStatement
, a jeśli spróbujesz zakodować coś, co nie jest poprawne dla MacroStatement
, kompilator natychmiast go złapie i zgłosi błąd zamiast nie wiedzieć, dopóki coś się nie pojawi środowisko uruchomieniowe.
Przeszczepianie makr na Haskell, Python, Java, Scala itp. Nie jest trudne, ponieważ te języki nie są homoikoniczne; jest to trudne, ponieważ języki nie są dla nich zaprojektowane, i działa najlepiej, gdy hierarchia AST twojego języka jest projektowana od podstaw, aby była badana i obsługiwana przez system makr. Kiedy pracujesz z językiem, który został zaprojektowany z myślą o metaprogramowaniu od samego początku, makra są znacznie prostsze i łatwiejsze w obsłudze!