Mam tylko ograniczoną wiedzę na temat Lisp (próbuję nauczyć się trochę w wolnym czasie), ale o ile rozumiem, makra Lisp pozwalają wprowadzać nowe konstrukcje językowe i składnię, opisując je w samym Lisp. Oznacza to, że nowy konstrukt można dodać jako bibliotekę bez zmiany kompilatora / interpretera Lisp.
To podejście bardzo różni się od innych języków programowania. Na przykład, jeśli chciałbym rozszerzyć Pascala o nowy rodzaj pętli lub jakiś szczególny idiom, musiałbym rozszerzyć składnię i semantykę języka, a następnie zaimplementować tę nową funkcję w kompilatorze.
Czy istnieją inne języki programowania spoza rodziny Lisp (tj. Oprócz Common Lisp, Scheme, Clojure (?), Racket (?) Itd.), Które oferują podobną możliwość rozszerzenia języka w obrębie samego języka?
EDYTOWAĆ
Unikaj długiej dyskusji i bądź konkretny w swoich odpowiedziach. Zamiast długiej listy języków programowania, które można rozszerzyć w taki czy inny sposób, chciałbym zrozumieć z koncepcyjnego punktu widzenia, co jest specyficzne dla makr Lisp jako mechanizmu rozszerzenia i które języki programowania inne niż Lisp oferują pewną koncepcję to jest blisko nich.
źródło
Odpowiedzi:
Scala też to umożliwia (w rzeczywistości został świadomie zaprojektowany do obsługi definicji nowych konstrukcji językowych, a nawet kompletnych DSL).
Oprócz funkcji wyższego rzędu, lambdas i curry, które są powszechne w językach funkcjonalnych, istnieją tutaj specjalne funkcje językowe *:
a.and(b)
jest to równoważne za and b
formą infixdo wywołań funkcji jednoparametrowych możesz użyć nawiasów klamrowych zamiast normalnych nawiasów klamrowych - to (wraz z curry) pozwala pisać takie rzeczy jak
gdzie
withPrintWriter
jest prostą metodą z dwiema listami parametrów, z których każda zawiera jeden parametrmyAssert(() => x > 3)
w krótszej formie jakomyAssert(x > 3)
Tworzenie przykładowego DSL jest szczegółowo omówione w rozdziale 11. Języki specyficzne dla domeny w Scali darmowej książki Programowanie Scala .
* Nie mam na myśli, że są one unikalne dla Scali, ale przynajmniej nie wydają się być zbyt powszechne. Nie jestem jednak ekspertem w językach funkcjonalnych.
źródło
Perl pozwala na wstępne przetwarzanie jego języka. Chociaż nie jest to często używane do radykalnej zmiany składni w języku, można to zobaczyć w niektórych ... dziwnych modułach:
Istnieje również moduł, który pozwala Perlowi uruchamiać kod, który wygląda tak, jakby został napisany w Pythonie.
Bardziej nowoczesnym podejściem do tego w Perlu byłoby użycie Filter :: Simple (jeden z podstawowych modułów w Perlu 5).
Zauważ, że wszystkie te przykłady dotyczą Damiana Conwaya, zwanego „Szalonym Doktorem Perla”. To wciąż niezwykle potężna zdolność perla do przekręcania języka tak, jak się chce.
Więcej dokumentacji na temat tej i innych alternatyw znajduje się w perlfilter .
źródło
Haskell
Haskell ma „Szablon Haskell” oraz „Quasiquotation”:
http://www.haskell.org/haskellwiki/Template_Haskell
http://www.haskell.org/haskellwiki/Quasiquotation
Funkcje te pozwalają użytkownikom znacznie zwiększyć składnię języka poza normalnymi środkami. Są one również rozwiązywane w czasie kompilacji, co moim zdaniem jest bardzo ważne (przynajmniej w przypadku języków kompilowanych) [1].
Już raz użyłem quasiquotacji w Haskell, aby stworzyć zaawansowany moduł dopasowywania wzorców w języku podobnym do C:
[1] W przeciwnym razie poniższe rozszerzenie kwalifikuje się jako rozszerzenie składni:
runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"
co oczywiście jest dużą ilością bzdur.źródło
forM
).Tcl ma długą historię obsługi rozszerzalnej składni. Na przykład, oto implementacja pętli, która iteruje trzy zmienne (aż do zatrzymania) nad kardynałami, ich kwadratami i sześcianami:
To byłoby wtedy użyte w ten sposób:
Tego rodzaju technika jest szeroko stosowana w programowaniu Tcl, a kluczem do jej zdrowego działania są polecenia
upvar
iuplevel
(upvar
wiąże zmienną nazwaną w innym zakresie ze zmienną lokalną iuplevel
uruchamia skrypt w innym zakresie: w obu przypadkach1
wskazuje, że zakres, o którym mowa, to osoba dzwoniąca). Jest również często używany w kodzie, który łączy się z bazami danych (uruchamiając jakiś kod dla każdego wiersza w zestawie wyników), w Tk dla GUI (do wiązania powiązań zwrotnych ze zdarzeniami) itp.To jednak tylko ułamek tego, co zostało zrobione. Osadzonym językiem nawet nie musi być Tcl; może to być praktycznie wszystko (o ile równoważy nawiasy klamrowe - wszystko staje się strasznie składniowe, jeśli to nieprawda - co stanowi ogromną większość programów), a Tcl może po prostu wysłać do wbudowanego języka obcego, jeśli to konieczne. Przykłady tego obejmują osadzanie C w celu zaimplementowania poleceń Tcl i równoważne z Fortran. (Prawdopodobnie wszystkie wbudowane polecenia Tcl są wykonywane w ten sposób, ponieważ w rzeczywistości są one tylko standardową biblioteką, a nie samym językiem.)
źródło
Jest to częściowo kwestia semantyki. Podstawową ideą Lisp jest to, że programem są dane, którymi sam można manipulować. Często używane języki w rodzinie Lisp, takie jak Scheme, tak naprawdę nie pozwalają dodawać nowej składni w sensie parsera; to tylko rozdzielone spacjami listy. Po prostu dlatego, że składnia jądra robi tak niewiele, możesz z niej stworzyć prawie każdą konstrukcję semantyczną . Scala (omówione poniżej) jest podobna: reguły nazw zmiennych są tak liberalne, że można z nich łatwo tworzyć ładne DSL (pozostając w obrębie tych samych podstawowych zasad składni).
Te języki, chociaż nie pozwalają na zdefiniowanie nowej składni w sensie filtrów Perla, mają wystarczająco elastyczny rdzeń, że można jej używać do budowania DSL i dodawania konstrukcji językowych.
Ważną wspólną cechą jest to, że pozwalają definiować konstrukcje językowe, które działają tak dobrze, jak wbudowane, używając funkcji ujawnionych przez języki. Stopień wsparcia dla tej funkcji jest różny:
sin()
,round()
itp, bez jakikolwiek sposób zaimplementować własną.static_cast<target_type>(input)
,dynamic_cast<>()
,const_cast<>()
,reinterpret_cast<>()
) może być emulowane przy użyciu funkcji szablonu, który motywujące zastosowań dlalexical_cast<>()
,polymorphic_cast<>()
,any_cast<>()
, ....for(;;){}
,while(){}
,if(){}else{}
,do{}while()
,synchronized(){}
,strictfp{}
) i nie pozwalają zdefiniować własne. Zamiast tego Scala definiuje abstrakcyjną składnię, która umożliwia wywoływanie funkcji za pomocą wygodnej składni podobnej do struktury kontrolnej, a biblioteki używają jej do skutecznego definiowania nowych struktur kontrolnych (np.react{}
W bibliotece aktorów).Możesz także spojrzeć na niestandardową funkcjonalność składni Mathematiki w pakiecie Notation . (Technicznie jest to rodzina Lisp, ale niektóre funkcje rozszerzalności zostały wykonane inaczej, a także zwykła rozszerzalność Lisp.)
źródło
(defmacro ...)
. Właściwie to teraz portuję ten język na Racket, dla zabawy. Ale zgadzam się, że jest to coś niezbyt przydatnego, ponieważ składnia wyrażeń S jest wystarczająca dla większości możliwych użytecznych semantycznych.(define-macro ...)
ekwiwalent, który z kolei może wykorzystywać dowolny rodzaj analizy wewnętrznej.Rebol brzmi prawie tak, jak to opisujesz , ale trochę z boku.
Zamiast definiować określoną składnię, wszystko w Rebol jest wywołaniem funkcji - nie ma słów kluczowych. (Tak, możesz przedefiniować
if
iwhile
jeśli naprawdę tego chcesz). Na przykład jest toif
stwierdzenie:if
jest funkcją, która przyjmuje 2 argumenty: warunek i blok. Jeśli warunek jest spełniony, blok jest oceniany. Brzmi jak większość języków, prawda? Cóż, blok jest strukturą danych, nie ogranicza się do kodu - jest to na przykład blok bloków, a szybki przykład elastyczności „kodu to dane”:Tak długo, jak będziesz mógł trzymać się reguł składni, rozszerzenie tego języka będzie w przeważającej części niczym więcej niż definiowaniem nowych funkcji. Niektórzy użytkownicy przenoszą na przykład funkcje Rebol 3 do Rebol 2.
źródło
Ruby ma dość elastyczną składnię, myślę, że jest to sposób na „rozszerzenie języka w obrębie samego języka”.
Przykładem jest rake . Jest napisany w Ruby, to jest Ruby, ale wygląda jak make .
Aby sprawdzić niektóre możliwości, możesz poszukać słów kluczowych Ruby i metaprogramowania .
źródło
Poszerzenie składni w sposób, w jaki mówisz, pozwala tworzyć języki specyficzne dla domeny. Być może więc najbardziej przydatnym sposobem na sformułowanie pytania jest to, jakie inne języki mają dobre wsparcie dla języków specyficznych dla domeny?
Ruby ma bardzo elastyczną składnię i rozkwitło tam wiele DSL, takich jak rake. Groovy zawiera wiele z tej dobroci. Obejmuje również transformacje AST, które są bardziej bezpośrednio analogiczne do makr Lisp.
R, język obliczeń statystycznych, pozwala funkcjom nie docenić ich argumentów. Wykorzystuje to, aby utworzyć DSL do określania formuły regresji. Na przykład:
oznacza „dopasuj linię w postaci k0 + k1 * a + k2 * b do wartości w y”.
oznacza „dopasuj linię w postaci k0 + k1 * a + k2 * b + k3 * a * b do wartości w y.”
I tak dalej.
źródło
Converge to kolejny język metaprogramowania inny niż lispy. Do pewnego stopnia również C ++ się kwalifikuje.
Prawdopodobnie MetaOCaml jest dość daleki od Lisp. Aby uzyskać zupełnie inny styl rozszerzania składni, ale mimo to dość potężny, spójrz na CamlP4 .
Nemerle jest innym rozszerzalnym językiem z metaprogramowaniem w stylu Lisp, chociaż jest on bliższy językom takim jak Scala.
I sama Scala wkrótce stanie się takim językiem.
Edycja: Zapomniałem o najciekawszym przykładzie - JetBrains MPS . Jest nie tylko bardzo odległy od wszystkiego Lispish, to nawet nietekstowy system programowania z edytorem działającym bezpośrednio na poziomie AST.
Edycja2: Aby odpowiedzieć na zaktualizowane pytanie - w makrach Lisp nie ma nic wyjątkowego i wyjątkowego. Teoretycznie każdy język może zapewnić taki mechanizm (nawet zrobiłem to ze zwykłym C). Wszystko czego potrzebujesz to dostęp do swojego AST i możliwość wykonania kodu w czasie kompilacji. Pomocna może być refleksja (zapytania dotyczące typów, istniejących definicji itp.).
źródło
Prolog pozwala definiować nowe operatory, które są tłumaczone na złożone terminy o tej samej nazwie. Na przykład definiuje to
has_cat
operator i definiuje go jako predykat do sprawdzania, czy lista zawiera atomcat
:W
xf
sposób, któryhas_cat
jest operatorem przyrostek; użyciefx
spowoduje, że stanie się on operatorem przedrostka ixfx
uczyni go operatorem przedrostka , przyjmując dwa argumenty. Sprawdź ten link, aby uzyskać więcej informacji na temat definiowania operatorów w Prologu.źródło
TeX całkowicie brakuje na liście. Wszyscy to wiecie, prawda? Wygląda to mniej więcej tak:
… Z wyjątkiem tego, że możesz przedefiniować składnię bez ograniczeń. Każdemu (!) Tokenowi w języku można przypisać nowe znaczenie. ConTeXt to pakiet makr, który zastąpił nawiasy klamrowe kwadratowymi nawiasami klamrowymi:
Bardziej powszechny pakiet makr LaTeX również redefiniuje język dla swoich celów, np. Dodając
\begin{environment}…\end{environment}
składnię.Ale to nie koniec. Technicznie równie dobrze można przedefiniować tokeny, aby przeanalizować następujące elementy:
Tak, absolutnie możliwe. Niektóre pakiety używają tego do definiowania małych języków specyficznych dla domeny. Na przykład pakiet TikZ definiuje zwięzłą składnię rysunków technicznych, która umożliwia:
Co więcej, TeX jest kompletny, więc możesz dosłownie wszystko z nim zrobić . Nigdy nie widziałem, aby był w pełni wykorzystany, ponieważ byłoby to zupełnie bezcelowe i bardzo skomplikowane, ale jest całkowicie możliwe, aby poniższy kod był analizowalny tylko poprzez redefiniowanie tokenów (ale prawdopodobnie przekroczyłoby to fizyczne granice parsera, z powodu sposób, w jaki jest zbudowany):
źródło
Boo pozwala na znaczne dostosowanie języka w czasie kompilacji dzięki makrom syntaktycznym.
Boo ma „rozszerzalny potok kompilatora”. Oznacza to, że kompilator może wywołać Twój kod, aby wykonać transformacje AST w dowolnym momencie potoku kompilatora. Jak wiecie, takie rzeczy jak Java's Generics lub Cq's Linq to tylko transformacje składniowe w czasie kompilacji, więc jest to dość potężne.
W porównaniu z Lisp, główną zaletą jest to, że działa przy dowolnej składni. Boo używa składni inspirowanej Pythonem, ale prawdopodobnie mógłbyś napisać rozszerzalny kompilator ze składnią C lub Pascal. A ponieważ makro jest oceniane podczas kompilacji, nie ma negatywnego wpływu na wydajność.
Minusy, w porównaniu do Lisp, to:
Na przykład w ten sposób możesz zaimplementować nową strukturę kontroli:
stosowanie:
który następnie jest tłumaczony podczas kompilacji na coś takiego:
(Niestety dokumentacja online Boo jest zawsze beznadziejnie przestarzała i nawet nie obejmuje takich zaawansowanych rzeczy. Najlepszą dokumentacją dla języka, który znam, jest ta książka: http://www.manning.com/rahien/ )
źródło
Ocena Mathematica opiera się na dopasowaniu i zamianie wzorców. Pozwala to tworzyć własne struktury kontrolne, zmieniać istniejące struktury kontrolne lub zmieniać sposób oceny wyrażeń. Na przykład możesz zaimplementować „logikę rozmytą” (nieco uproszczoną):
Zastępuje to ocenę predefiniowanych operatorów logicznych &&, || ,! oraz
If
klauzula wbudowana .Możesz odczytać te definicje jak definicje funkcji, ale prawdziwe znaczenie to: Jeśli wyrażenie pasuje do wzorca opisanego po lewej stronie, zostaje zastąpione wyrażeniem po prawej stronie. Możesz zdefiniować swoją własną klauzulę If w następujący sposób:
SetAttributes[..., HoldRest]
mówi ewaluatorowi, że powinien ocenić pierwszy argument przed dopasowaniem wzorca, ale wstrzymać ocenę do końca, dopóki wzorzec nie zostanie dopasowany i zastąpiony.Jest to szeroko stosowane w standardowych bibliotekach Mathematica w celu np. Zdefiniowania funkcji,
D
która przyjmuje wyrażenie i ocenia jego symboliczną pochodną.źródło
Metalua jest językiem i kompilatorem kompatybilnym z Lua, który to zapewnia.
Różnice z Lisp:
Przykładem zastosowania jest implementacja dopasowania wzorców podobnego do ML.
Zobacz także: http://lua-users.org/wiki/MetaLua
źródło
Jeśli szukasz języków z możliwością rozszerzenia, powinieneś rzucić okiem na Smalltalk.
W Smalltalk jedyny sposób na programowanie jest rozszerzenie języka. Nie ma różnicy między IDE, bibliotekami a samym językiem. Wszystkie są tak splecione, że Smalltalk jest często nazywany środowiskiem, a nie językiem.
Nie piszesz samodzielnych aplikacji w Smalltalk, zamiast tego rozszerzasz środowisko językowe.
Sprawdź http://www.world.st/, aby uzyskać garść zasobów i informacji.
Chciałbym polecić Pharo jako dialekt wejścia do świata Smalltalk: http://pharo-project.org
Mam nadzieję, że to pomogło!
źródło
Istnieją narzędzia, które pozwalają tworzyć niestandardowe języki bez pisania od zera całego kompilatora. Na przykład istnieje Spoofax , który jest narzędziem do transformacji kodu: wprowadzasz gramatykę wejściową i reguły transformacji (napisane w bardzo deklaratywny sposób na wysokim poziomie), a następnie możesz wygenerować kod źródłowy Java (lub inny język, jeśli ci zależy) z niestandardowego języka zaprojektowanego przez Ciebie.
Możliwe byłoby więc przyjęcie gramatyki języka X, zdefiniowanie gramatyki języka X '(X z własnymi rozszerzeniami) i transformacja X' → X, a Spoofax wygeneruje kompilator X '→ X.
Obecnie, jeśli dobrze rozumiem, najlepszą obsługą jest Java, z rozwijaną obsługą C # (a przynajmniej tak słyszałem). Technikę tę można jednak zastosować w dowolnym języku ze gramatyką statyczną (więc np. Prawdopodobnie nie w Perlu ).
źródło
Dalej jest inny język, który jest wysoce rozszerzalny. Wiele implementacji Forth składa się z małego jądra napisanego w asemblerze lub C, a następnie reszta języka jest napisana w samym Forth.
Istnieje również kilka języków opartych na stosach, które są inspirowane przez Fortha i mają tę funkcję, na przykład Factor .
źródło
Funge-98
Funkcja odcisku palca Funge-98 pozwala na pełną restrukturyzację całej składni i semantyki języka. Ale tylko jeśli implementator dostarczy mechanizm odcisków palców, który pozwolił użytkownikowi programowo zmienić język (teoretycznie jest to możliwe do wdrożenia w ramach normalnej składni i semantyki Funge-98). Jeśli tak, to można dosłownie sprawić, aby reszta pliku (lub dowolne części pliku) działały jako C ++ lub Lisp (lub cokolwiek zechce).
http://quadium.net/funge/spec98.html#Fingerprints
źródło
Aby uzyskać to, czego szukasz, naprawdę potrzebujesz tych nawiasów i braku składni. Kilka języków opartych na składni może się zbliżyć, ale to nie to samo, co prawdziwe makro.
źródło