Écrire un interprète Haskell à Haskell
un exercice de programmation classique consiste à écrire un interpréteur Lisp/Scheme dans Lisp/Scheme. La puissance de la pleine langue peut être mis à profit pour produire un interprète pour un sous-ensemble de la langue.
y a-t-il un exercice similaire pour Haskell? J'aimerais implémenter un sous-ensemble de Haskell en utilisant Haskell comme moteur. Bien sûr, il peut être fait, mais y a-t-il des ressources en ligne disponibles à regarder?
Voici la trame de fond.
j'explore l'idée d'utiliser Haskell comme langue pour explorer certains des concepts dans un cours structures discrètes j'enseigne. Pour ce semestre , je me suis installé sur Miranda , une langue plus petite qui a inspiré Haskell. Miranda fait environ 90% de ce que j'aimerais qu'il fasse, mais Haskell fait environ 2000%. :)
Donc, mon idée est de créer un langage qui a exactement les caractéristiques D'Haskell que je voudrais et rejette tout le reste. Au fur et à mesure que les élèves progressent, je peux sélectivement "allumer" diverses fonctions une fois qu'ils maîtrisent les bases.
les niveaux de langue" pédagogiques "ont été utilisés avec succès pour enseigner Java et Scheme . En limitant ce qu'ils peuvent faire, vous pouvez les empêcher de se tirer dans le pied alors qu'ils maîtrisent encore la syntaxe et les concepts vous sont en train d'enseigner. Et vous pouvez offrir de meilleurs messages d'erreur.
15 réponses
j'aime votre objectif, mais c'est un gros travail. Quelques conseils:
-
j'ai travaillé sur GHC, et vous ne voulez aucune partie des sources. Hugs est une implémentation beaucoup plus simple et plus propre, mais malheureusement C'est en C.
-
C'est une petite pièce du puzzle, mais Mark Jones a écrit un beau papier appelé taper Haskell dans Haskell qui serait un bon point de départ pour votre front.
bonne chance! L'identification des niveaux de langue pour Haskell, avec les preuves à l'appui de la salle de classe, serait d'un grand avantage pour la communauté et certainement un résultat publiable!
il y a un analyseur Haskell complet: http://hackage.haskell.org/package/haskell-src-exts
une fois que vous l'avez analysé, enlever ou rejeter certaines choses est facile. J'ai fait ça pour tryhaskell.org pour rejeter les déclarations d'importation, pour soutenir les définitions de haut niveau, etc.
il suffit de parcourir le module:
parseModule :: String -> ParseResult Module
alors vous avez un AST pour un module:
Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]
le type Decl est extensif: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t%3ADecl
tout ce que vous avez à faire est de définir une liste blanche -- des déclarations, des importations, des symboles, de la syntaxe qui sont disponibles, puis de parcourir L'AST et de jeter une" erreur d'analyse " sur tout ce que vous ne voulez pas qu'ils soient encore au courant. Vous pouvez utiliser la valeur SrcLoc attachée à chaque noeud de L'AST:
data SrcLoc = SrcLoc
{ srcFilename :: String
, srcLine :: Int
, srcColumn :: Int
}
pas besoin de ré-implémenter Haskell. Si vous voulez fournir des erreurs de compilation plus conviviales, analysez simplement le code, filtrez-le, envoyez-le au compilateur, et analysez la sortie du compilateur. Si c'est un "ne pouvait pas correspondre expected type A contre inféré a -> b
" alors vous savez que c'est probablement trop peu d'arguments à une fonction.
sauf si vous voulez vraiment passer du temps à mettre en œuvre Haskell à partir de zéro ou de jouer avec les internes de Hugs, ou certains stupide implémentation, je pense que tu devrais juste filtrer ce qui est passé à GHC. De cette façon, si vos élèves veulent prendre leur base de code et l'emmener à l'étape suivante et écrire un vrai code Haskell, la transition est transparente.
voulez-vous construire votre interprète à partir de zéro? Commencez par mettre en œuvre un langage fonctionnel plus facile comme le calcul lambda ou une variante lisp. Pour ce dernier il y a un très bon wikibook appelé écrivez-vous un schéma en 48 heures donnant une introduction cool et pragmatique dans les techniques d'analyse et d'interprétation.
interpréter Haskell à la main sera beaucoup plus complexe car vous aurez à traiter avec très complexe des fonctionnalités comme typeclasses, un système de type extrêmement puissant (type-inférence!) et paresseux-évaluation (techniques de réduction).
donc vous devriez définir un assez petit sous-ensemble de Haskell à travailler avec et puis peut-être commencer par étendre le Schéma-Exemple étape par étape.
ajout:
notez qu'à Haskell, vous avez un accès complet à l'API interprètes (au moins sous GHC), y compris les analyseurs, les compilateurs et bien sûr les interprètes.
le paquet à utiliser est indice (langue.Haskell.* ) . Je n'ai malheureusement pas trouvé de tutoriels en ligne sur ce sujet ni essayé par moi-même, mais il semble très prometteur.
créer un langage qui a exactement les caractéristiques de Haskell que je voudrais et rejette tout le reste. Au fur et à mesure que les élèves progressent, je peux sélectivement "allumer" diverses fonctions une fois qu'ils maîtrisent les bases.
je suggère une solution plus simple (comme dans moins de travail impliqué) à ce problème. Au lieu de créer une implémentation Haskell où vous pouvez désactiver les fonctionnalités, enveloppez un compilateur Haskell avec un programme qui vérifie d'abord que le le code n'utilise aucune fonctionnalité que vous désactivez, et utilise ensuite le compilateur prêt à l'emploi pour le compiler.
qui serait similaire à HLint (et aussi un peu de son opposé):
HLint (anciennement Dr.Haskell) lit les programmes Haskell et suggère des changements qui, espérons-le, les rendront plus faciles à lire. HLint permet également de désactiver les suggestions, et pour ajouter vos propres suggestions.
- implémentez votre propre HLint "suggestions" pour ne pas utiliser les fonctionnalités que vous n'autorisez pas
- désactiver toutes les suggestions hlint standard.
- Faire de votre wrapper exécuter votre modifiée HLint comme une première étape
- Traiter HLint suggestions comme des erreurs. C'est-à-dire, si HLint "s'est plaint" alors le programme ne passe pas à l'étape de compilation
Baskell est un enseignement de mise en œuvre, http://hackage.haskell.org/package/baskell
vous pourriez commencer par choisir juste, disons, le système de type à mettre en œuvre. C'est aussi compliqué qu'un interprète pour Scheme, http://hackage.haskell.org/package/thih
la série de compilateurs EHC est probablement le meilleur pari: elle est activement développée et semble être exactement ce que vous voulez - une série de petits compilateurs/interprètes lambda calculi culminant en Haskell '98.
mais vous pouvez aussi regarder les différents langages développés dans les Types et langages de programmation de Pierce , ou L'interpréteur D'hélium (un Haskell infirme destiné aux étudiants http://en.wikipedia.org/wiki/Helium_ (Haskell) ).
si vous recherchez un sous-ensemble d'Haskell facile à implémenter, vous pouvez supprimer les classes de type et la vérification de type. Sans classes de type, vous n'avez pas besoin d'inférence de type pour évaluer le code Haskell.
j'ai écrit un" 15197092020 "compilateur de sous-ensembles Haskell pour un défi de Golf de Code. Il prend le code de sous-ensemble Haskell sur l'entrée et produit le code C sur la sortie. Je suis désolé il n'y a pas une version plus lisible disponible; j'ai enlevé les définitions imbriquées par la main dans le processus de fabrication de l'auto-compilation.
pour un étudiant intéressé à implémenter un interpréteur pour un sous-ensemble de Haskell, je recommande de commencer par les fonctionnalités suivantes:
-
évaluation différée. Si L'interprète est à Haskell, vous n'aurez peut-être rien à faire.
-
définition de fonction avec arguments et gardes assortis de motifs. Seul souci à propos de variables, contre, nil, et
_
patterns. -
syntaxe d'expression Simple:
-
les littéraux Entiers
-
caractères littéraux
-
[]
(néant) -
Fonction de l'application (associativité à gauche)
-
Infix
:
(cons, droite associative) -
parenthèse
-
noms variables
-
noms de fonction
-
plus concrètement, écrivez un interpréteur qui peut exécuter ceci:
-- 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)
La vérification de Type est une caractéristique essentielle de Haskell. Cependant, passer de rien à un compilateur de type Haskell est très difficile. Si vous commencez par écrire un interpréteur pour ce qui précède, ajouter la vérification de type à cela devrait être moins intimidant.
Vous pourriez regarder Heureux (un yacc-comme analyseur en Haskell) qui a un Haskell analyseur.
voir si hélium serait une meilleure base pour construire sur la base de la norme haskell.
Uhc/Ehc est une série de compilateurs permettant / désactivant diverses fonctionnalités Haskell. http://www.cs.uu.nl/wiki/Ehc/WebHome#What_is_UHC_And_EHC
on m'a dit que Idris a un analyseur assez compact, pas sûr si c'est vraiment approprié pour l'altération, mais il est écrit dans Haskell.
Andrej Bauer langage de programmation Zoo a une petite mise en œuvre d'un langage de programmation purement fonctionnelle quelque peu insolent nommé "minihaskell". C'est environ 700 lignes de OCaml, donc très facile à digérer.
le site contient également des versions jouets des langages de programmation ML-style, Prolog-style et OO.
ne pensez-vous pas qu'il serait plus facile de prendre les sources du GHC et de rayer ce que vous ne voulez pas, que ce serait d'écrire votre propre interprète de Haskell à partir de zéro? De façon générale, il devrait y avoir un lot moins d'effort requis pour enlever caractéristiques au lieu de créer/ajouter des caractéristiques.
GHC est écrit à Haskell de toute façon, donc techniquement cela reste avec votre question D'un Haskell interprète écrit en Haskell.
il ne serait probablement pas trop difficile de faire le tout lié statiquement et ensuite seulement distribuer votre GHCi personnalisé, de sorte que les étudiants ne peuvent pas charger d'autres modules source Haskell. Quant à la quantité de travail qu'il faudrait pour les empêcher de charger d'autres fichiers d'objets Haskell, Je n'en ai aucune idée. Vous souhaiterez peut-être désactiver FFI aussi, si vous avez un tas de tricheurs dans vos classes :)
la raison pour laquelle il y a tant d'interprètes LISP est que LISP est fondamentalement un prédécesseur de JSON: un format simple pour encoder des données. Cela rend la partie frontale assez facile à manipuler. Comparé à cela, Haskell, en particulier avec les Extensions de langue, n'est pas la langue la plus facile à analyser. Ce sont quelques constructions syntaxiques qui semblent difficiles à corriger:
- opérateurs avec configurables priorité, l'associativité et la fixité,
- commentaires emboîtés
- disposition générale
- syntaxe de motif
-
do
- blocs et desugaring à monadique code
à l'exception peut-être des opérateurs, chacun d'entre eux pourrait être abordé par les étudiants après leur cours de construction de compilateurs, mais cela détournerait l'attention de la façon dont Haskell fonctionne réellement. En plus de cela, vous pourriez ne pas vouloir mettre en œuvre toutes les constructions syntaxiques de Haskell directement, mais à la place mettre en œuvre des laissez-passer pour se débarrasser d'eux. Ce qui nous amène au cœur littéral de la question, jeu de mots parfaitement prévu.
ma suggestion est d'implémenter le contrôle de typographie et un interpréteur pour Core
au lieu de full Haskell. Ces deux tâches sont déjà assez complexes en soi.
Ce langage, tout en restant un langage fonctionnel fortement typé, est beaucoup moins compliqué à traiter en termes d'optimisation et de génération de code.
Cependant, il est toujours indépendant de la base de la machine.
Par conséquent, GHC l'utilise comme langage intermédiaire et traduit la plupart des constructions syntaxiques de Haskell en elle.
de plus, vous ne devriez pas hésiter à utiliser GHC (ou un autre compilateur) frontend.
Je ne considère pas cela comme de la tricherie puisque les LISPs personnalisés utilisent L'analyseur du système LISP de l'hôte (au moins pendant le bootstrapping). Nettoyer les bribes de Core
et les présenter aux élèves, avec le code original, devrait permettez-vous de donner un aperçu de ce que fait le frontend, et pourquoi il est préférable de ne pas le réimposer.
voici quelques liens vers la documentation de Core
utilisée dans GHC: