Języki programowania z mechanizmem rozszerzania składni podobnym do Lispa [zamknięte]

20

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.

Giorgio
źródło
6
Lisp ma jeszcze jedną sztuczkę oprócz zwykłych makr. „Makra czytników” umożliwiają przechwytywanie i rozszerzanie składni parsera w czasie wykonywania, więc nawet podstawowa struktura znaczników języka jest pod kontrolą.
ddyer
@ddyer: Dzięki, nie wiedziałem o tym: kolejny temat do dodania do mojej listy czytelniczej.
Giorgio
Założę się, że metaprogramowanie Ruby może to spełnić.
Przypon
1
twoje pytanie jest sprzeczne, najpierw pytasz o listę, a potem pytasz o informacje koncepcyjne, co to jest ???
Proszę o listę, ale nie ogólną (nie tylko język programowania, który można w jakiś sposób rozszerzyć), ponieważ byłby to zbyt ogólny. Chciałbym wiedzieć o językach, które można rozszerzyć w sposób podobny do Lisp (rozszerzenie jest zdefiniowane przy użyciu tego samego języka, który rozszerza, a nie jakiegoś meta-języka). Odpowiedzi Pétera Töröka, Thomasa Edinga, SK-logic i mechanicznego ślimaka wydają się być najbliższe temu, co miałem na myśli. Nadal jednak muszę uważnie przeczytać całą odpowiedź.
Giorgio

Odpowiedzi:

19

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 *:

  • brak operatorów - wszystko jest funkcją, ale nazwy funkcji mogą zawierać znaki specjalne, takie jak „+”, „-” lub „:”
  • kropki i nawiasy klamrowe można pominąć w wywołaniach metod jednoparametrowych, tzn. a.and(b)jest to równoważne z a and bformą infix
  • do 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

    val file = new File("example.txt")
    
    withPrintWriter(file) {
      writer => writer.println("this line is a function call parameter")
    }
    

    gdzie withPrintWriterjest prostą metodą z dwiema listami parametrów, z których każda zawiera jeden parametr

  • parametry by-name pozwalają pominąć pustą listę parametrów w lambdas, umożliwiając pisanie wywołań jak myAssert(() => 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.

Péter Török
źródło
1
+1: Interesujące. Czy to oznacza, że ​​aby rozszerzyć składnię, mogę zdefiniować nowe klasy i że podpisy metod zapewnią nową składnię jako produkt uboczny?
Giorgio
@Giorgio, w zasadzie tak.
Péter Török,
Linki już nie działają.
Nate Glenn,
13

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:

  • Acme :: Bleach dla naprawdę czystego kodu
  • Acme :: Morse za pisanie w kodzie Morse'a
  • Lingua :: Romana :: Perligata do pisania w języku łacińskim (na przykład rzeczowniki są zmiennymi, a liczba, wielkość liter i deklinacja zmieniają rodzaj rzeczownika nextum ==> $ next, nexta ==> @next)

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 .

użytkownik40980
źródło
13

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:

moveSegment :: [Token] -> Maybe (SegPath, SegPath, [Token])
moveSegment [hc| HC_Move_Segment(@s, @s); | s1 s2 ts |] = Just (mkPath s1, mkPath s2, ts)
moveSegment _ = Nothing

[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.

Thomas Eding
źródło
3
Istnieją również inne funkcje Haskell, które zasadniczo pozwalają na niestandardową składnię, takie jak tworzenie własnego operatora lub automatyczne curry w połączeniu z funkcjami lambda (rozważ np. Typowe użycie forM).
Xion
Szczerze uważam, że operatorzy curry i niestandardowi nie kwalifikują się. Pozwalają one na porządne posługiwanie się językiem, ale nie pozwalają na dodanie / new / funkcjonalność do języka. TH i QQ. W ścisłym tego słowa znaczeniu, TH i QQ nie robią dokładnie tego, do czego zostały zaprojektowane, ale pozwalają naprawdę „wyjść z języka” w czasie kompilacji.
Thomas Eding,
1
„W przeciwnym razie poniższe kwalifikuje się jako rozszerzenie składni ...”: bardzo dobry punkt.
Giorgio
12

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:

proc loopCard23 {cardinalVar squareVar cubeVar body} {
    upvar 1 $cardinalVar cardinal $squareVar square $cubeVar cube

    # We borrow a 'for' loop for the implementation...
    for {set cardinal 0} true {incr cardinal} {
        set square [expr {$cardinal ** 2}]
        set cube [expr {$cardinal ** 3}]

        uplevel 1 $body
    }
}

To byłoby wtedy użyte w ten sposób:

loopCard23 a b c {
    puts "got triplet: $a, $b, $c"
    if {$c > 400} {
        break
    }
}

Tego rodzaju technika jest szeroko stosowana w programowaniu Tcl, a kluczem do jej zdrowego działania są polecenia upvari uplevel( upvarwiąże zmienną nazwaną w innym zakresie ze zmienną lokalną i upleveluruchamia skrypt w innym zakresie: w obu przypadkach 1wskazuje, ż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.)

Donal Fellows
źródło
10

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:

  • Wiele starszych języków przewidzianych wbudowanych funkcji, takich jak sin(), round()itp, bez jakikolwiek sposób zaimplementować własną.
  • C ++ zapewnia ograniczone wsparcie. Na przykład niektóre wbudowanych słów kluczowych, takich jak odlewy ( 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ń dla lexical_cast<>(), polymorphic_cast<>(), any_cast<>(), ....
  • Java ma wbudowaną struktur kontrolnych ( 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.)

Ślimak mechaniczny
źródło
2
Źle. Lisp ma rzeczywiście pozwalają na dodawanie absolutnie jakiejkolwiek nowej składni. Jak w tym przykładzie: meta-alternative.net/pfront.pdf - to nic innego jak makro Lisp.
SK-logic
Wydaje się, że zostało to zaimplementowane w języku specjalnie zaprojektowanym do tworzenia DSL . Oczywiście możesz stworzyć język w rodzinie Lisp, który również zapewnia takie funkcje; Miałem na myśli, że ta funkcja nie jest podstawowym pomysłem Lisp obsługiwanym przez często używany Lisps (np. Scheme). Edytowane w celu wyjaśnienia.
Ślimak mechaniczny
Ten „język do tworzenia DSL” to zbiór makr zbudowany na dość typowym, minimalistycznym Lisp. Można go łatwo przenieść na dowolny inny Lisp (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.
SK-logic,
I Schemat nie różni się niczym od Common Lisp, począwszy od R6RS oficjalnie i nieoficjalnie przez wieki, ponieważ zapewnia (define-macro ...)ekwiwalent, który z kolei może wykorzystywać dowolny rodzaj analizy wewnętrznej.
SK-logic,
8

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ć ifi whilejeśli naprawdę tego chcesz). Na przykład jest to ifstwierdzenie:

if now/time < 12:00 [print "Morning"]

ifjest 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”:

SomeArray: [ [foo "One"] [bar "Two"] [baz "Three"] ]
foreach action SomeArray [action/1: 'print] ; Change the data
if now/time < 12:00 SomeArray/2 ; Use the data as code - right now, if now/time < 12:00 [print "Two"]

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.

Izkata
źródło
7

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 .

knut
źródło
13
„elastyczna składnia” BARDZO różni się od „rozszerzalnej składni”. Minęło sporo czasu, odkąd programowałem w Ruby, ale Rake wygląda jak dobrze zaprojektowane użycie wbudowanej składni. Innymi słowy, nie jest to przykład.
Thomas Eding
2
Czy to nie kwestia stopnia, a nie rodzaju? Jak rozróżniłbyś język „rozszerzalnej składni”, który może rozszerzać niektóre aspekty jego składni, ale nie innych, od języka „elastycznej składni”?
nadchodząca burza
1
Jeśli linia jest rozmyta, to odsuńmy ją, aby uważano, że C obsługuje rozszerzalną składnię.
Thomas Eding,
Aby połączyć się z twoim przykładem, narysuję tutaj linię: język z rozszerzalną składnią może tworzyć rake, który wygląda jak make. Język z elastyczną składnią można rozszerzyć (ha, fajne mieszanie językowe) do kompilacji i uruchamiania plików makefile. Twoje zdanie na temat stopnia jest jednak dobre. Być może niektóre języki pozwalają na kompilację marki Make, ale nie Python. Inni pozwolą na jedno i drugie.
Clayton Stanley,
Przetwarzanie kompletnego języka powinno umożliwiać przetwarzanie plików Make. Elastyczność składni nie jest czynnikiem większym niż wyzwanie.
Rig
7

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:

y ~ a + b

oznacza „dopasuj linię w postaci k0 + k1 * a + k2 * b do wartości w y”.

y ~ a * b

oznacza „dopasuj linię w postaci k0 + k1 * a + k2 * b + k3 * a * b do wartości w y.”

I tak dalej.

Martin C. Martin
źródło
2
Transformacje AST Groovy są bardzo szczegółowe w porównaniu do makr Lisp lub Clojure. Na przykład przykład Groovy 20+ linii na groovy.codehaus.org/Global+AST+Transformacje można przepisać jako jedną krótką linię w Clojure, np. „(This (println ~ message)). Mało tego, nie musisz także kompilować słoika, pisać metadanych ani żadnych innych rzeczy na tej stronie Groovy.
Vorg van Geir,
7

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.).

SK-logika
źródło
Twój link Scala wyraźnie mówi, że proponowane makra „nie mogą zmienić składni Scali”. (Interesujące, wymienia to jako różnicę między tą propozycją a makrami preprocesora C / C ++!)
ruakh 12.12
@ruakh, tak, jest to to samo podejście co Converge and Template Haskell - aplikacja makr jest wyraźnie oznaczona i nie można jej mieszać ze „normalną” składnią. Ale wewnątrz możesz mieć dowolną składnię, którą lubisz, więc prawdopodobnie jest to składnia rozszerzalna. Niestety wymóg „non-lisp” ogranicza twoje opcje do takich języków.
SK-logic
„Nawet zrobiłem to ze zwykłym C”: Jak to możliwe?
Giorgio
@Giorgio, oczywiście zmodyfikowałem kompilator (dodałem makra i kompilację modułów przyrostowych, co w rzeczywistości jest całkiem naturalne dla C).
SK-logic,
Dlaczego niezbędny jest dostęp do AST?
Elliot Gorokhovsky
6

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_catoperator i definiuje go jako predykat do sprawdzania, czy lista zawiera atom cat:

:- op(500, xf, has_cat).
X has_cat :- member(cat, X).

?- [apple, cat, orange] has_cat.
true ;
false.

W xfsposób, który has_catjest operatorem przyrostek; użycie fxspowoduje, że stanie się on operatorem przedrostka i xfxuczyni go operatorem przedrostka , przyjmując dwa argumenty. Sprawdź ten link, aby uzyskać więcej informacji na temat definiowania operatorów w Prologu.

Ambroz Bizjak
źródło
5

TeX całkowicie brakuje na liście. Wszyscy to wiecie, prawda? Wygląda to mniej więcej tak:

Some {\it ``interesting''} example.

… 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:

Some \it[``interesting''] example.

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:

Some <it>“interesting”</it> example.

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:

\foreach \angle in {0, 30, ..., 330} 
  \draw[line width=1pt] (\angle:0.82cm) -- (\angle:1cm);

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):

for word in [Some interesting example.]:
    if word == interesting:
        it(word)
    else:
        word
Konrad Rudolph
źródło
5

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:

  • Praca z AST nie jest tak elegancka jak praca z wyrażeniami s
  • Ponieważ makro jest wywoływane w czasie kompilacji, nie ma dostępu do danych środowiska wykonawczego.

Na przykład w ten sposób możesz zaimplementować nową strukturę kontroli:

macro repeatLines(repeatCount as int, lines as string*):
    for line in lines:
        yield [| print $line * $repeatCount |]

stosowanie:

repeatLines 2, "foo", "bar"

który następnie jest tłumaczony podczas kompilacji na coś takiego:

print "foo" * 2
print "bar" * 2

(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/ )

nikie
źródło
1
Dokumentacja internetowa dla tej funkcji jest obecnie zepsuta i sam nie piszę Boo, ale pomyślałem, że szkoda byłoby, gdyby została przeoczona. Doceniam informację zwrotną od modów i ponownie rozważę, w jaki sposób mogę przekazywać darmowe informacje w wolnym czasie.
Dan.
4

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ą):

fuzzy[a_ && b_]      := Min[fuzzy[a], fuzzy[b]]
fuzzy[a_ || b_]      := Max[fuzzy[a], fuzzy[b]]
fuzzy[!a_]           := 1-fuzzy[a]
If[fuzzy[a_], b_,c_] := fuzzy[a] * fuzzy[b] + fuzzy[!a] * fuzzy[c]

Zastępuje to ocenę predefiniowanych operatorów logicznych &&, || ,! oraz Ifklauzula 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:

myIf[True, then_, else_] := then
myIf[False, then_, else_] := else
SetAttributes[myIf, HoldRest]

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, Dktóra przyjmuje wyrażenie i ocenia jego symboliczną pochodną.

nikie
źródło
3

Metalua jest językiem i kompilatorem kompatybilnym z Lua, który to zapewnia.

  • Pełna zgodność ze źródłami Lua 5.1 i kodem bajtowym: czysta, elegancka semantyka i składnia, niesamowita moc ekspresji, dobre osiągi, niemal uniwersalna przenośność. - Kompletny system makr, podobny w sile do tego, co oferują dialekty Lisp lub Template Haskell; zmanipulowane programy mogą być postrzegane
    jako kod źródłowy, jako abstrakcyjne drzewa składniowe lub jako dowolna
    ich kombinacja , w zależności od tego, które z nich lepiej pasuje do zadania.
  • Dynamicznie rozszerzalny analizator składni, który umożliwia obsługę makr za pomocą składni, która ładnie łączy się z resztą języka.

  • Zestaw rozszerzeń językowych, wszystkie zaimplementowane jako zwykłe makra metalua.

Różnice z Lisp:

  • Nie zawracaj sobie głowy programistami, gdy nie piszą makr: składnia i semantyka języka powinny być najlepiej dostosowane do tych 95% przypadków, gdy nie piszemy makr.
  • Zachęć programistów do przestrzegania konwencji języka: nie tylko dzięki „najlepszym praktykom”, których nikt nie słucha, ale także oferując interfejs API, który ułatwia pisanie po Metalua. Czytelność innych programistów jest ważniejsza i trudniejsza do osiągnięcia niż czytelność kompilatorów, a do tego bardzo pomaga wspólny zestaw przestrzeganych konwencji.
  • Zapewnij jednak całą moc, z jaką chcesz się zmagać. Ani Lua, ani Metalua nie podlegają obowiązkowej niewoli i dyscyplinie, więc jeśli wiesz, co robisz, język nie będzie ci przeszkadzał.
  • Wyraź to oczywiste, gdy wydarzy się coś interesującego: wszystkie meta-operacje mają miejsce między + {...} a - {...} i wizualnie wystają poza zwykły kod.

Przykładem zastosowania jest implementacja dopasowania wzorców podobnego do ML.

Zobacz także: http://lua-users.org/wiki/MetaLua

Clement J.
źródło
Chociaż Lua nie jest naprawdę Lispem, twoja lista „różnic” jest dość podejrzana (jedynym istotnym przedmiotem jest ostatni). I, oczywiście, społeczność Lisp nie zgodziłaby się z twierdzeniem, że nie należy pisać / używać makr przez 95% czasu - sposób Lisp skłania się do czegoś takiego jak 95% czasu przy użyciu i pisaniu list DSL, oprócz makr, kierunek.
SK-logic,
2

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!

rev Bernat Romagosa
źródło
1
Brzmi interesująco.
Thomas Eding
1

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 ).

liori
źródło
1

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 .

Dave Kirby
źródło
0

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

Thomas Eding
źródło
dlaczego opublikowałeś to osobno zamiast dodawać do swojej poprzedniej odpowiedzi ?
komar
1
Ponieważ Funge nie ma nic wspólnego z Haskellem.
Thomas Eding
-1

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.

mike30
źródło