web-dev-qa-db-fra.com

É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 langue complète peut être exploitée pour produire un interprète pour un sous-ensemble de la langue.

Existe-t-il un exercice similaire pour Haskell? Je voudrais implémenter un sous-ensemble de Haskell en utilisant Haskell comme moteur. Bien sûr, cela peut être fait, mais existe-t-il des ressources en ligne à consulter?


J'explore l'idée d'utiliser Haskell comme langage pour explorer certains des concepts d'un cours Structures discrètes que j'enseigne. Pour ce semestre, j'ai choisi Miranda , une langue plus petite qui a inspiré Haskell. Miranda fait environ 90% de ce que j'aimerais qu'elle fasse, mais Haskell fait environ 2000%. :)

Mon idée est donc de créer un langage qui a exactement les caractéristiques de Haskell que j'aimerais et qui interdit tout le reste. Au fur et à mesure que les élèves progressent, je peux "activer" de manière sélective diverses fonctionnalités une fois qu'ils ont maîtrisé les bases.

Des "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 une balle dans le pied pendant qu'ils maîtrisent encore la syntaxe et les concepts que vous essayez d'enseigner. Et vous pouvez offrir de meilleurs messages d'erreur.

88
Barry Brown

J'adore ton objectif, mais c'est un gros travail. Quelques indices:

  • 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 ce qui serait un excellent point de départ pour votre front-end.

Bonne chance! Identifier les niveaux de langue pour Haskell, avec des preuves à l'appui de la classe, serait d'un grand avantage pour la communauté et certainement un résultat publiable!

75
Norman Ramsey

Il y a un analyseur Haskell complet: http://hackage.haskell.org/package/haskell-src-exts

Une fois que vous l'avez analysé, retirer ou interdire certaines choses est facile. J'ai fait cela pour tryhaskell.org pour interdire les instructions d'importation, pour prendre en charge les définitions de niveau supérieur, etc.

Analysez simplement le module:

parseModule :: String -> ParseResult Module

Vous avez alors un AST pour un module:

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

Le type Decl est étendu: 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 devez faire est de définir une liste blanche - de quelles déclarations, importations, symboles, syntaxe est disponible, puis parcourez le AST et lancez une "erreur d'analyse" sur tout ce que vous ne ' Je ne veux pas qu'ils soient au courant. Vous pouvez utiliser la valeur SrcLoc attachée à chaque nœud de l'AST:

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

Il n'est pas nécessaire de réimplémenter Haskell. Si vous souhaitez fournir des erreurs de compilation plus conviviales, il suffit d'analyser le code, de le filtrer, de l'envoyer au compilateur et d'analyser la sortie du compilateur. S'il s'agit d'un "ne peut pas faire correspondre le type attendu à un inféré a -> b "alors vous savez que c'est probablement trop peu d'arguments pour une fonction.

À moins que vous ne souhaitiez vraiment passer du temps à implémenter Haskell à partir de zéro ou à jouer avec les composants internes de Hugs, ou à une implémentation stupide, je pense que vous devriez simplement filtrer ce qui est transmis à GHC. De cette façon, si vos élèves veulent prendre leur base de code et passer à l'étape suivante et écrire du vrai code Haskell à part entière, la transition est transparente.

37
Christopher Done

Voulez-vous construire votre interprète à partir de zéro? Commencez par implémenter un langage fonctionnel plus facile comme le calcul lambda ou une variante LISP. Pour ce dernier, il existe un wikibook assez sympa appelé Écrivez-vous un schéma en 48 heures donnant une introduction cool et pragmatique aux techniques d'analyse et d'interprétation.

Interpréter Haskell à la main sera beaucoup plus complexe car vous devrez faire face à des fonctionnalités très complexes comme les classes de type, un système de type extrêmement puissant (inférence de type!) Et l'évaluation paresseuse (techniques de réduction).

Vous devez donc définir un assez petit sous-ensemble de Haskell avec lequel travailler, puis peut-être commencer par étendre l'exemple de schéma pas à pas.

Une addition:

Notez que dans Haskell, vous avez un accès complet à l'API des interprètes (au moins sous GHC), y compris les analyseurs, les compilateurs et bien sûr les interprètes.

Le package à utiliser est hint (Language.Haskell. *) . Je n'ai malheureusement ni trouvé de tutoriels en ligne sur ce sujet ni essayé par moi-même mais cela semble assez prometteur.

24
Dario

créer un langage qui a exactement les caractéristiques de Haskell que j'aimerais et interdit tout le reste. Au fur et à mesure que les élèves progressent, je peux "activer" de manière sélective diverses fonctionnalités une fois qu'ils ont maîtrisé 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 des fonctionnalités, enveloppez un compilateur Haskell avec un programme qui vérifie d'abord que le code n'utilise aucune fonctionnalité que vous interdisez, puis utilise le compilateur prêt à l'emploi pour le compiler.

Ce serait similaire à HLint (et aussi à son contraire):

HLint (anciennement le Dr Haskell) lit les programmes Haskell et suggère des changements qui, espérons-le, les rendent plus faciles à lire. HLint facilite également la désactivation des suggestions indésirables et l'ajout de vos propres suggestions personnalisées.

  • Implémentez vos propres "suggestions" HLint pour ne pas utiliser les fonctionnalités que vous n'autorisez pas
  • Désactivez toutes les suggestions HLint standard.
  • Faites en sorte que votre wrapper exécute votre HLint modifié dans un premier temps
  • Traitez les suggestions HLint comme des erreurs. Autrement dit, si HLint "s'est plaint" alors le programme ne passe pas à l'étape de compilation
20
yairchu

Baskell est une implémentation pédagogique, http://hackage.haskell.org/package/baskell

Vous pouvez commencer par choisir simplement, disons, le système de type à implémenter. C'est à peu près aussi compliqué qu'un interprète pour Scheme, http://hackage.haskell.org/package/thih

16
Don Stewart

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 de calculs lambda culminant en Haskell '98.

Mais vous pouvez également regarder les différents langages développés dans Pierce Types et langages de programmation , ou l'interprète Helium (un Haskell paralysé destiné aux étudiants http://en.wikipedia.org/wiki/Helium_(Haskell) ).

6
gwern

Si vous recherchez un sous-ensemble de 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 compilateur de sous-ensemble Haskell à compilation automatique pour un défi Code Golf. Il prend le code de sous-ensemble Haskell en entrée et produit du code C en sortie. Je suis désolé, il n'y a pas de version plus lisible disponible; J'ai levé les définitions imbriquées à la main dans le processus de compilation automatique.

Pour un étudiant intéressé à implémenter un interprète pour un sous-ensemble de Haskell, je recommanderais de commencer par les fonctionnalités suivantes:

  • Évaluation paresseuse. Si l'interprète est à Haskell, vous n'aurez peut-être rien à faire pour cela.

  • Définitions de fonctions avec des arguments et des gardes assortis au modèle. Ne vous inquiétez que des variables, des inconvénients, de zéro et de _ motifs.

  • Syntaxe d'expression simple:

    • Littéraux entiers

    • Littéraux de caractères

    • [] (néant)

    • Application de fonction (associative gauche)

    • Infix : (contre, associative droite)

    • Parenthèse

    • Noms de variables

    • Noms des fonctions

Plus concrètement, écrivez un interprète 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 cruciale de Haskell. Cependant, passer de rien à un compilateur Haskell à vérification de type est très difficile. Si vous commencez par écrire un interpréteur pour ce qui précède, l'ajout d'une vérification de type devrait être moins intimidant.

6
Joey Adams

This pourrait être une bonne idée - faites une petite version de NetLogo dans Haskell. Ici est le petit interprète.

3
Claudiu

Vous pourriez regarder Happy (un analyseur similaire à yacc dans Haskell) qui a un analyseur Haskell.

3
Kathy Van Stone

voir si hélium ferait une meilleure base de construction que le haskell standard.

2
Martin DeMello

Uhc/Ehc est une série de compilateurs activant/désactivant diverses fonctionnalités Haskell. http://www.cs.uu.nl/wiki/Ehc/WebHome#What_is_UHC_And_EHC

2
ja.

Andrej Bauer Programming Language Zoo a une petite implémentation d'un langage de programmation purement fonctionnel quelque peu effrontément nommé "minihaskell". Il s'agit d'environ 700 lignes d'OCaml, donc très faciles à digérer.

Le site contient également des versions jouets des styles de programmation ML, Prolog et OO.

2
niklas

On m'a dit que Idris a un analyseur assez compact, je ne sais pas s'il est vraiment adapté à la modification, mais il est écrit en Haskell.

2
Jeff Burdges

Ne pensez-vous pas qu'il serait plus facile de prendre les sources du GHC et de supprimer ce que vous ne voulez pas, que ce serait d'écrire votre propre interprète Haskell à partir de zéro? De manière générale, il devrait y avoir beaucoup moins d'efforts impliqués dans la suppression des fonctionnalités au lieu de créer/ajouter des fonctionnalités.

GHC est écrit en Haskell de toute façon, donc techniquement cela reste avec votre question d'un interprète Haskell écrit en Haskell.

Il ne serait probablement pas trop difficile de lier le tout statiquement et de ne distribuer ensuite que votre GHCi personnalisé, afin que les étudiants ne puissent 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 voudrez peut-être aussi désactiver FFI, si vous avez un tas de tricheurs dans vos classes :)

1
Mark Rushakoff

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 les données. Cela rend la partie frontale assez facile à manipuler. Par rapport à cela, Haskell, en particulier avec les extensions linguistiques, n'est pas la langue la plus facile à analyser. Voici quelques constructions syntaxiques qui semblent difficiles à obtenir correctement:

  • opérateurs avec priorité, associativité et fixité configurables,
  • commentaires imbriqués
  • règle de mise en page
  • syntaxe de modèle
  • do- bloque et désucide en code monadique

Chacun d'entre eux, à l'exception peut-être des opérateurs, 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 ne voudrez peut-être pas implémenter directement toutes les constructions syntaxiques de Haskell, mais implémentez plutôt des passes pour vous en débarrasser. Ce qui nous amène au cœur littéral du problème, un jeu de mots bien intentionné.

Ma suggestion est d'implémenter typechecking et un interpréteur pour Core au lieu de Haskell complet. Ces deux tâches sont déjà assez complexes en elles-mêmes. Ce langage, tout en étant un langage fonctionnel fortement typé, est beaucoup moins compliqué à gérer en termes d'optimisation et de génération de code. Cependant, il est toujours indépendant de la machine sous-jacente. Par conséquent, GHC l'utilise comme langage intermédiaire et y traduit la plupart des constructions syntaxiques de Haskell.

De plus, vous ne devriez pas hésiter à utiliser l'interface de GHC (ou d'un autre compilateur). Je ne considérerais pas cela comme de la triche puisque les LISP personnalisés utilisent l'analyseur du système Host LISP (au moins pendant le bootstrapping). Nettoyer Core extraits et les présenter aux étudiants, avec le code d'origine, devrait vous permettre de donner un aperçu de ce que fait le frontend, et pourquoi il est préférable de ne pas le réimplémenter.

Voici quelques liens vers la documentation de Core utilisée dans GHC:

0
MauganRa