Schreiben Sie einen Haskell-Dolmetscher in Haskell

90

Eine klassische Programmierübung besteht darin, einen Lisp / Scheme-Interpreter in Lisp / Scheme zu schreiben. Die Leistung der vollständigen Sprache kann genutzt werden, um einen Interpreter für eine Teilmenge der Sprache zu erstellen.

Gibt es eine ähnliche Übung für Haskell? Ich möchte eine Teilmenge von Haskell mit Haskell als Engine implementieren. Natürlich ist dies möglich, aber gibt es Online-Ressourcen, die Sie sich ansehen können?


Hier ist die Hintergrundgeschichte.

Ich untersuche die Idee, Haskell als Sprache zu verwenden, um einige der Konzepte in einem von mir unterrichteten Kurs für diskrete Strukturen zu untersuchen . Für dieses Semester habe ich mich für Miranda entschieden , eine kleinere Sprache, die Haskell inspiriert hat. Miranda macht ungefähr 90% von dem, was ich möchte, aber Haskell macht ungefähr 2000%. :) :)

Meine Idee ist es also, eine Sprache zu erstellen, die genau die Funktionen von Haskell enthält, die ich möchte, und alles andere nicht zulässt. Während die Schüler Fortschritte machen, kann ich verschiedene Funktionen selektiv "einschalten", sobald sie die Grundlagen beherrschen.

Pädagogische "Sprachniveaus" wurden erfolgreich verwendet, um Java und Schema zu unterrichten . Indem Sie ihre Möglichkeiten einschränken, können Sie verhindern, dass sie sich in den Fuß schießen, während sie noch die Syntax und Konzepte beherrschen, die Sie vermitteln möchten. Und Sie können bessere Fehlermeldungen anbieten.

Barry Brown
quelle
Ich habe einen WIP-Haskell-Dialekt mit Typing Haskell in Haskell als Basis implementiert. Es gibt hier eine Demo davon chrisdone.com/toys/duet-delta Es ist nicht bereit für die Veröffentlichung von Open Source, aber ich könnte die Quelle bei Interesse mit Ihnen teilen.
Christopher Done

Antworten:

76

Ich liebe dein Ziel, aber es ist ein großer Job. Ein paar Tipps:

  • Ich habe an GHC gearbeitet, und Sie wollen keinen Teil der Quellen. Hugs ist eine viel einfachere und sauberere Implementierung, aber leider in C.

  • Es ist ein kleines Puzzleteil, aber Mark Jones hat ein wunderschönes Papier namens Typing Haskell in Haskell geschrieben, das ein guter Ausgangspunkt für Ihr Frontend wäre.

Viel Glück! Die Ermittlung der Sprachniveaus für Haskell mit Belegen aus dem Klassenzimmer wäre für die Community von großem Nutzen und definitiv ein publizierbares Ergebnis!

Norman Ramsey
quelle
2
Ich frage mich, ob der Kommentar zu GHC noch korrekt ist. GHC ist komplex, aber gut dokumentiert. Insbesondere die internen NotesInformationen sind hilfreich, um Details auf niedriger Ebene zu verstehen, und das Kapitel über GHC in Die Architektur von Open Source-Anwendungen bietet einen hervorragenden Überblick auf hoher Ebene.
Sjy
37

Es gibt einen vollständigen Haskell-Parser: http://hackage.haskell.org/package/haskell-src-exts

Sobald Sie es analysiert haben, ist es einfach, bestimmte Dinge zu entfernen oder zu verbieten. Ich habe dies für tryhaskell.org getan, um Importanweisungen zu verbieten, Definitionen der obersten Ebene zu unterstützen usw.

Analysieren Sie einfach das Modul:

parseModule :: String -> ParseResult Module

Dann haben Sie einen AST für ein Modul:

Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]    

Der Decl-Typ ist umfangreich: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t%3ADecl

Alles, was Sie tun müssen, ist eine weiße Liste zu definieren - welche Deklarationen, Importe, Symbole und Syntax verfügbar sind, dann den AST zu durchlaufen und einen "Analysefehler" auf alles zu werfen, von dem Sie nicht möchten, dass sie es noch wissen. Sie können den SrcLoc-Wert verwenden, der an jeden Knoten im AST angehängt ist:

data SrcLoc = SrcLoc
     { srcFilename :: String
     , srcLine :: Int
     , srcColumn :: Int
     }

Haskell muss nicht erneut implementiert werden. Wenn Sie benutzerfreundlichere Kompilierungsfehler bereitstellen möchten, analysieren Sie einfach den Code, filtern Sie ihn, senden Sie ihn an den Compiler und analysieren Sie die Compilerausgabe. Wenn es sich um ein "nicht erwarteter Typ a gegen abgeleitet a -> b" handelt, wissen Sie, dass es wahrscheinlich zu wenige Argumente für eine Funktion gibt.

Wenn Sie nicht wirklich wirklich Zeit damit verbringen möchten, Haskell von Grund auf neu zu implementieren oder sich mit den Interna von Hugs oder einer dummen Implementierung herumzuschlagen, sollten Sie einfach filtern, was an GHC übergeben wird. Auf diese Weise ist der Übergang transparent, wenn Ihre Schüler ihre Codebasis verwenden und zum nächsten Schritt übergehen und echten, vollwertigen Haskell-Code schreiben möchten.

Christopher Done
quelle
24

Möchten Sie Ihren Dolmetscher von Grund auf neu erstellen? Beginnen Sie mit der Implementierung einer einfacheren Funktionssprache wie dem Lambda-Kalkül oder einer Lisp-Variante. Für letztere gibt es ein ziemlich schönes Wikibook namens Write yourself a Scheme in 48 Stunden , das eine coole und pragmatische Einführung in Parsing- und Interpretationstechniken bietet.

Das manuelle Interpretieren von Haskell wird viel komplexer, da Sie sich mit hochkomplexen Funktionen wie Typklassen, einem äußerst leistungsfähigen Typensystem (Typinferenz!) Und einer verzögerten Bewertung (Reduktionstechniken) befassen müssen.

Sie sollten also eine kleine Teilmenge von Haskell definieren, mit der Sie arbeiten möchten, und dann möglicherweise das Schema-Beispiel Schritt für Schritt erweitern.

Zusatz:

Beachten Sie, dass Sie in Haskell vollen Zugriff auf die Interpreter-API haben (zumindest unter GHC), einschließlich Parser, Compiler und natürlich Interpreter.

Das zu verwendende Paket ist ein Hinweis (Language.Haskell. *) . Ich habe leider weder Online-Tutorials dazu gefunden noch selbst ausprobiert, aber es sieht vielversprechend aus.

Dario
quelle
12
Beachten Sie, dass die Typinferenz tatsächlich ein sehr einfacher Algorithmus mit 20 bis 30 Zeilen ist. Es ist wunderschön in seiner Einfachheit. Lazy Evaluation ist auch nicht so schwer zu kodieren. Ich würde sagen, die Schwierigkeit liegt in der verrückten Syntax, dem Mustervergleich und nur der großen Menge an Dingen in der Sprache.
Claudiu
Interessant - Können Sie Links für die Typ-Inferenz-Algen posten?
Dario
5
Ja, lesen Sie dieses kostenlose Buch - cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26 -, es befindet sich auf Seite 273 (289 des PDF). Der Alg-Pseudocode befindet sich auf P296.
Claudiu
1
Es gibt auch eine Implementierung eines (der?) Typinferenz- / Überprüfungsalgorithmus in " Die Implementierung funktionaler Programmiersprachen ".
Phil Armstrong
1
Typinferenz mit Typklassen ist jedoch nicht einfach.
Christopher Done
20

Erstellen Sie eine Sprache, die genau die Funktionen von Haskell enthält, die ich möchte, und die alles andere nicht zulässt. Während die Schüler Fortschritte machen, kann ich verschiedene Funktionen selektiv "einschalten", sobald sie die Grundlagen beherrschen.

Ich schlage eine einfachere (wie bei weniger Arbeit) Lösung für dieses Problem vor. Anstatt eine Haskell-Implementierung zu erstellen, in der Sie Features deaktivieren können, schließen Sie einen Haskell-Compiler mit einem Programm ein, das zunächst überprüft, ob der Code keine nicht zugelassenen Features verwendet, und dann den vorgefertigten Compiler zum Kompilieren verwendet.

Das wäre ähnlich wie HLint (und auch das Gegenteil davon):

HLint (ehemals Dr. Haskell) liest Haskell-Programme und schlägt Änderungen vor, die sie hoffentlich leichter lesbar machen. HLint macht es auch einfach, unerwünschte Vorschläge zu deaktivieren und eigene benutzerdefinierte Vorschläge hinzuzufügen.

  • Implementieren Sie Ihre eigenen HLint- "Vorschläge", um die Funktionen, die Sie nicht zulassen, nicht zu verwenden
  • Deaktivieren Sie alle Standard-HLint-Vorschläge.
  • Lassen Sie Ihren Wrapper als ersten Schritt Ihren modifizierten HLint ausführen
  • Behandeln Sie HLint-Vorschläge als Fehler. Das heißt, wenn HLint sich "beschwert", geht das Programm nicht zur Kompilierungsphase über
Yairchu
quelle
6

Die EHC-Compilerserie ist wahrscheinlich die beste Wahl: Sie wird aktiv entwickelt und scheint genau das zu sein, was Sie wollen - eine Reihe kleiner Lambda-Kalkül-Compiler / Interpreter, die in Haskell '98 gipfeln.

Sie können sich aber auch die verschiedenen Sprachen ansehen, die in Pierces Typen und Programmiersprachen entwickelt wurden , oder den Helium-Interpreter (ein verkrüppelter Haskell für Studenten http://en.wikipedia.org/wiki/Helium_(Haskell) ).

gwern
quelle
6

Wenn Sie nach einer Teilmenge von Haskell suchen, die einfach zu implementieren ist, können Sie auf Typklassen und Typprüfung verzichten. Ohne Typklassen benötigen Sie keine Typinferenz, um den Haskell-Code auszuwerten.

Ich habe einen selbstkompilierenden Haskell-Subset-Compiler für eine Code Golf-Herausforderung geschrieben. Es verwendet Haskell-Teilmengencode bei der Eingabe und erzeugt C-Code bei der Ausgabe. Es tut mir leid, dass keine besser lesbare Version verfügbar ist. Ich habe verschachtelte Definitionen von Hand aufgehoben, um sie selbst zu kompilieren.

Für einen Studenten, der einen Dolmetscher für eine Teilmenge von Haskell implementieren möchte, würde ich empfehlen, mit den folgenden Funktionen zu beginnen:

  • Faule Bewertung. Wenn sich der Dolmetscher in Haskell befindet, müssen Sie möglicherweise nichts dafür tun.

  • Funktionsdefinitionen mit musterangepassten Argumenten und Schutzvorrichtungen. Sorgen Sie sich nur um Variablen, Nachteile, Null und _Muster.

  • Einfache Ausdruckssyntax:

    • Ganzzahlige Literale

    • Zeichenliterale

    • [] (Null)

    • Funktionsanwendung (links assoziativ)

    • Infix :(Nachteile, rechter Assoziativ)

    • Klammer

    • Variablennamen

    • Funktionsnamen

Schreiben Sie konkreter einen Interpreter, der dies ausführen kann:

-- tail :: [a] -> [a]
tail (_:xs) = xs

-- append :: [a] -> [a] -> [a]
append []     ys = ys
append (x:xs) ys = x : append xs ys

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
zipWith _ _      _      = []

-- showList :: (a -> String) -> [a] -> String
showList _    []     = '[' : ']' : []
showList show (x:xs) = '[' : append (show x) (showItems show xs)

-- showItems :: (a -> String) -> [a] -> String
showItems show []     = ']' : []
showItems show (x:xs) = ',' : append (show x) (showItems show xs)

-- fibs :: [Int]
fibs = 0 : 1 : zipWith add fibs (tail fibs)

-- main :: String
main = showList showInt (take 40 fibs)

Die Typprüfung ist ein entscheidendes Merkmal von Haskell. Es ist jedoch sehr schwierig, von nichts zu einem Haskell-Compiler mit Typprüfung überzugehen. Wenn Sie zunächst einen Interpreter für das oben Gesagte schreiben, sollte das Hinzufügen einer Typprüfung weniger entmutigend sein.

Joey Adams
quelle
"Faule Bewertung. Wenn der Dolmetscher in Haskell ist, müssen Sie möglicherweise nichts dafür tun." Dies kann nicht wahr sein. Weitere Informationen zum Implementieren eines faulen Interpreters in Haskell finden Sie in Naylors Artikel unter haskell.org/wikiupload/0/0a/TMR-Issue10.pdf .
Jared Updike
3

Sie könnten sich Happy (einen Yacc-ähnlichen Parser in Haskell) ansehen, der einen Haskell-Parser hat.

Kathy Van Stone
quelle
3

Dies könnte eine gute Idee sein - erstellen Sie eine winzige Version von NetLogo in Haskell. Hier ist der kleine Dolmetscher.

Claudiu
quelle
Die Links sind tot. Gibt es eine Chance, dass dieser Inhalt noch woanders existiert? Ich wäre neugierig ...
Nicolas Payette
hmm es war ein Blogbeitrag und ich habe keine Ahnung, mit welchen Schlüsselwörtern ich danach suchen soll. Eine gute Lektion, um umfangreichere Informationen bei der Bereitstellung eines Links aufzunehmen ...
Claudiu
1
Eine Google-Suche nach "netlogo haskell" taucht auf ... diese Frage. Jedenfalls keine große Sache. Vielen Dank!
Nicolas Payette
2

Sehen Sie, ob Helium eine bessere Basis als Standard-Haskell darstellt.

Martin DeMello
quelle
2

Mir wurde gesagt, dass Idris einen ziemlich kompakten Parser hat, nicht sicher, ob er wirklich für Änderungen geeignet ist, aber er ist in Haskell geschrieben.

Jeff Burdges
quelle
2

Der Programmiersprachen-Zoo von Andrej Bauer hat eine kleine Implementierung einer rein funktionalen Programmiersprache mit dem etwas frechen Namen "Minihaskell". Es sind ungefähr 700 Linien OCaml, also sehr leicht zu verdauen.

Die Site enthält auch Spielzeugversionen der Programmiersprachen ML-Stil, Prolog-Stil und OO.

Niklas
quelle
1

Glauben Sie nicht, dass es einfacher wäre, die GHC-Quellen zu entfernen, was Sie nicht wollen, als Ihren eigenen Haskell-Interpreter von Grund auf neu zu schreiben? Im Allgemeinen sollte das Entfernen von Features viel weniger Aufwand erfordern als das Erstellen / Hinzufügen von Features.

GHC ist sowieso in Haskell geschrieben, technisch gesehen bleibt das bei Ihrer Frage nach einem in Haskell geschriebenen Haskell-Dolmetscher.

Es wäre wahrscheinlich nicht allzu schwierig, das Ganze statisch zu verknüpfen und dann nur Ihr angepasstes GHCi zu verteilen, damit die Schüler keine anderen Haskell-Quellmodule laden können. Ich habe keine Ahnung, wie viel Arbeit erforderlich wäre, um zu verhindern, dass andere Haskell-Objektdateien geladen werden. Vielleicht möchten Sie auch FFI deaktivieren, wenn Sie eine Reihe von Betrügern in Ihren Klassen haben :)

Mark Rushakoff
quelle
1
Dies ist nicht so einfach, wie es sich anhört, da viele Funktionen von anderen abhängen. Aber vielleicht will das OP einfach nicht Prelude importieren und stattdessen sein eigenes bereitstellen. Die meisten von Haskell, die Sie sehen, sind normale Funktionen, keine spezifischen Merkmale der Laufzeit. (Aber natürlich viel sind .)
jrockway
0

Der Grund, warum es so viele LISP-Interpreter gibt, ist, dass LISP im Grunde ein Vorgänger von JSON ist: ein einfaches Format zum Codieren von Daten. Dies macht das Frontend-Teil ziemlich einfach zu handhaben. Im Vergleich dazu ist Haskell, insbesondere bei Spracherweiterungen, nicht die am einfachsten zu analysierende Sprache. Dies sind einige syntaktische Konstrukte, die schwierig zu klingen klingen:

  • Operatoren mit konfigurierbarer Priorität, Assoziativität und Fixität,
  • verschachtelte Kommentare
  • Layoutregel
  • Mustersyntax
  • do- Blockiert und Desugaring zu monadischem Code

Jedes dieser Probleme, mit Ausnahme der Bediener, könnte von den Studenten nach ihrem Compiler-Konstruktionskurs angegangen werden, aber es würde den Fokus von der tatsächlichen Funktionsweise von Haskell ablenken. Darüber hinaus möchten Sie möglicherweise nicht alle syntaktischen Konstrukte von Haskell direkt implementieren, sondern stattdessen Durchläufe implementieren, um sie zu entfernen. Das bringt uns zum wörtlichen Kern des Themas, Wortspiel voll beabsichtigt.

Mein Vorschlag ist, Typechecking und einen Interpreter für Coreanstelle von vollständigem Haskell zu implementieren . Beide Aufgaben sind für sich genommen schon recht kompliziert. Diese Sprache ist zwar immer noch eine stark typisierte funktionale Sprache, aber im Hinblick auf Optimierung und Codegenerierung viel weniger kompliziert zu handhaben. Es ist jedoch immer noch unabhängig von der zugrunde liegenden Maschine. Daher verwendet GHC es als Zwischensprache und übersetzt die meisten syntaxischen Konstrukte von Haskell in sie.

Darüber hinaus sollten Sie nicht davor zurückschrecken, das Frontend von GHC (oder einem anderen Compiler) zu verwenden. Ich würde das nicht als Betrug betrachten, da benutzerdefinierte LISPs den Parser des Host-LISP-Systems verwenden (zumindest während des Bootstrapings). Wenn Sie CoreSnippets bereinigen und den Schülern zusammen mit dem Originalcode präsentieren, können Sie einen Überblick darüber geben, was das Frontend tut und warum es vorzuziehen ist, es nicht erneut zu implementieren.

Hier sind einige Links zur Dokumentation von CoreGHC:

MauganRa
quelle