Scrivi un interprete Haskell a Haskell


90

Un classico esercizio di programmazione è scrivere un interprete Lisp / Scheme in Lisp / Scheme. La potenza della lingua completa può essere sfruttata per produrre un interprete per un sottoinsieme della lingua.

Esiste un esercizio simile per Haskell? Vorrei implementare un sottoinsieme di Haskell utilizzando Haskell come motore. Ovviamente si può fare, ma ci sono risorse online disponibili da consultare?


Ecco il retroscena.

Sto esplorando l'idea di utilizzare Haskell come linguaggio per esplorare alcuni dei concetti in un corso di Discrete Structures che sto insegnando. Per questo semestre ho scelto Miranda , una lingua più piccola che ha ispirato Haskell. Miranda fa circa il 90% di quello che vorrei che facesse, ma Haskell fa circa il 2000%. :)

Quindi la mia idea è di creare un linguaggio che abbia esattamente le caratteristiche di Haskell che vorrei e che non permetta tutto il resto. Man mano che gli studenti progrediscono, posso "attivare" selettivamente varie funzionalità una volta acquisite le nozioni di base.

I "livelli linguistici" pedagogici sono stati utilizzati con successo per insegnare Java e Scheme . Limitando ciò che possono fare, puoi impedire loro di spararsi ai piedi mentre stanno ancora padroneggiando la sintassi e i concetti che stai cercando di insegnare. E puoi offrire messaggi di errore migliori.


Ho un dialetto WIP Haskell implementato con Typing Haskell in Haskell come base. Ce n'è una demo qui chrisdone.com/toys/duet-delta Non è pronto per il rilascio pubblico open source, ma potrei condividere la fonte con te se interessato.
Christopher Fatto il

Risposte:


76

Amo il tuo obiettivo, ma è un grande lavoro. Un paio di suggerimenti:

  • Ho lavorato su GHC e tu non vuoi nessuna parte dei sorgenti. Hugs è un'implementazione molto più semplice e pulita, ma sfortunatamente è in C.

  • È un piccolo pezzo del puzzle, ma Mark Jones ha scritto un bellissimo articolo intitolato Typing Haskell in Haskell che sarebbe un ottimo punto di partenza per il tuo front-end.

In bocca al lupo! Identificare i livelli linguistici per Haskell, con prove a sostegno della classe, sarebbe di grande beneficio per la comunità e sicuramente un risultato pubblicabile!


2
Mi chiedo se il commento su GHC sia ancora accurato. GHC è complesso, ma è abbastanza ben documentato. In particolare, gli interni Notessono utili nella comprensione dei dettagli di basso livello e il capitolo su GHC in The Architecture of Open-Source Applications fornisce un'eccellente panoramica di alto livello.
sjy

37

Esiste un parser Haskell completo: http://hackage.haskell.org/package/haskell-src-exts

Dopo averlo analizzato, rimuovere o vietare determinate cose è facile. L'ho fatto per tryhaskell.org per non consentire istruzioni di importazione, per supportare definizioni di primo livello, ecc.

Basta analizzare il modulo:

parseModule :: String -> ParseResult Module

Quindi hai un AST per un modulo:

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

Il tipo Decl è ampio: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t%3ADecl

Tutto quello che devi fare è definire una lista bianca - di quali dichiarazioni, importazioni, simboli, sintassi sono disponibili, quindi eseguire l'AST e lanciare un "errore di analisi" su tutto ciò di cui non vuoi che siano ancora a conoscenza. È possibile utilizzare il valore SrcLoc associato a ogni nodo nell'AST:

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

Non è necessario implementare nuovamente Haskell. Se si desidera fornire errori di compilazione più semplici, è sufficiente analizzare il codice, filtrarlo, inviarlo al compilatore e analizzare l'output del compilatore. Se si tratta di un "impossibile abbinare il tipo atteso a contro inferito a -> b", allora sai che probabilmente sono troppo pochi gli argomenti per una funzione.

A meno che tu non voglia davvero dedicare del tempo all'implementazione di Haskell da zero o scherzando con gli interni di Hugs, o qualche stupida implementazione, penso che dovresti semplicemente filtrare ciò che viene passato a GHC. In questo modo, se i tuoi studenti vogliono prendere la loro base di codice e portarla al passaggio successivo e scrivere un codice Haskell vero e proprio, la transizione è trasparente.


24

Vuoi costruire il tuo interprete da zero? Inizia con l'implementazione di un linguaggio funzionale più semplice come il lambda calcolo o una variante lisp. Per quest'ultimo c'è un bel wikibook chiamato Scrivi uno schema in 48 ore che offre un'introduzione fresca e pragmatica alle tecniche di analisi e interpretazione.

Interpretare Haskell a mano sarà molto più complesso poiché dovrai occuparti di caratteristiche molto complesse come le classi dei tipi, un sistema di tipi estremamente potente (inferenza di tipo!) E la valutazione pigra (tecniche di riduzione).

Quindi dovresti definire un piccolo sottoinsieme di Haskell con cui lavorare e poi magari iniziare estendendo l'esempio dello schema passo dopo passo.

Aggiunta:

Nota che in Haskell, hai pieno accesso all'API degli interpreti (almeno sotto GHC) inclusi parser, compilatori e ovviamente interpreti.

Il pacchetto da usare è hint (Language.Haskell. *) . Sfortunatamente non ho trovato tutorial online su questo né provato da solo, ma sembra abbastanza promettente.


12
Nota che l'inferenza del tipo è in realtà un algoritmo di 20-30 righe molto semplice. è bello nella sua semplicità. Anche la valutazione pigra non è così difficile da codificare. Direi che la difficoltà sta nella folle sintassi, nella corrispondenza dei modelli e solo nella grande quantità di cose nella lingua.
Claudiu

Interessante: puoi pubblicare link per gli algoritmi di inferenza del tipo?
Dario

5
Sì, dai un'occhiata a questo libro gratuito - cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26 -, è a pagina 273 (289 del pdf). Lo pseudocodice alg è su P296.
Claudiu

1
C'è anche un'implementazione di un (il?) Algoritmo di inferenza / verifica del tipo in " The Implementation of Functional Programming Languages ".
Phil Armstrong

1
Tuttavia, l'inferenza del tipo con le classi di tipo non è semplice.
Christopher Fatto il

20

creare un linguaggio che abbia esattamente le caratteristiche di Haskell che vorrei e che non permetta tutto il resto. Man mano che gli studenti progrediscono, posso "attivare" selettivamente varie funzionalità una volta acquisite le nozioni di base.

Suggerisco una soluzione più semplice (come in meno lavoro richiesto) a questo problema. Invece di creare un'implementazione Haskell in cui è possibile disattivare le funzionalità, racchiudere un compilatore Haskell con un programma che prima controlla che il codice non utilizzi alcuna funzionalità che non è consentita, quindi utilizza il compilatore già pronto per compilarlo.

Sarebbe simile a HLint (e anche un po 'il suo opposto):

HLint (ex Dr. Haskell) legge i programmi Haskell e suggerisce modifiche che si spera li rendano più facili da leggere. HLint rende anche facile disabilitare i suggerimenti indesiderati e aggiungere i tuoi suggerimenti personalizzati.

  • Implementa i tuoi "suggerimenti" HLint per non utilizzare le funzionalità che non permetti
  • Disabilita tutti i suggerimenti HLint standard.
  • Fai in modo che il tuo wrapper esegua il tuo HLint modificato come primo passaggio
  • Considera i suggerimenti di HLint come errori. Cioè, se HLint "si è lamentato", il programma non procede alla fase di compilazione


6

La serie di compilatori EHC è probabilmente la soluzione migliore: è sviluppata attivamente e sembra essere esattamente ciò che desideri: una serie di piccoli compilatori / interpreti lambda calculi culminati in Haskell '98.

Ma potresti anche guardare i vari linguaggi sviluppati nei tipi e nei linguaggi di programmazione di Pierce , o l'interprete Helium (un Haskell paralizzato destinato agli studenti http://en.wikipedia.org/wiki/Helium_(Haskell) ).


6

Se stai cercando un sottoinsieme di Haskell facile da implementare, puoi eliminare le classi di tipo e il controllo del tipo. Senza classi di tipo, non è necessaria l'inferenza del tipo per valutare il codice Haskell.

Ho scritto un compilatore di subset Haskell che si auto-compila per una sfida di Code Golf. Prende il codice del sottoinsieme Haskell in input e produce codice C in output. Mi dispiace che non sia disponibile una versione più leggibile; Ho rimosso manualmente le definizioni annidate nel processo di autocompilazione.

Per uno studente interessato a implementare un interprete per un sottoinsieme di Haskell, consiglierei di iniziare con le seguenti caratteristiche:

  • Valutazione pigra. Se l'interprete è in Haskell, potresti non dover fare nulla per questo.

  • Definizioni di funzioni con argomenti e guardie con corrispondenza di modelli. Preoccupati solo di variabili, svantaggi, zero e _modelli.

  • Sintassi delle espressioni semplici:

    • Letterali interi

    • Letterali carattere

    • [] (zero)

    • Applicazione della funzione (associativa sinistra)

    • Infix :(contro, diritto associativo)

    • Parentesi

    • Nomi variabili

    • Nomi delle funzioni

Più concretamente, scrivi un interprete che possa eseguire questo:

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

Il controllo del tipo è una caratteristica cruciale di Haskell. Tuttavia, passare dal nulla a un compilatore Haskell per il controllo del tipo è molto difficile. Se inizi scrivendo un interprete per quanto sopra, l'aggiunta del controllo del tipo dovrebbe essere meno scoraggiante.


"Valutazione pigra. Se l'interprete è in Haskell, potresti non dover fare nulla per questo." Questo potrebbe non esser vero. Vedi l'articolo di Naylor in haskell.org/wikiupload/0/0a/TMR-Issue10.pdf per maggiori informazioni sull'implementazione di un interprete pigro in Haskell.
Jared Updike


3

Questa potrebbe essere una buona idea: crea una piccola versione di NetLogo in Haskell. Ecco il minuscolo interprete.


I collegamenti sono morti. C'è qualche possibilità che questo contenuto esista ancora da qualche altra parte? Sarei curioso ...
Nicolas Payette

hmm era un post sul blog e non ho idea di quali parole chiave usare per cercarlo. Una buona lezione per includere informazioni più sostanziali quando si fornisce un collegamento ...
Claudiu

1
Una ricerca su Google per "netlogo haskell" si presenta ... questa domanda. Comunque, niente di grave. Grazie!
Nicolas Payette

2

vedere se l' elio sarebbe una base migliore su cui costruire rispetto all'hashkell ​​standard.



2

Mi è stato detto che Idris ha un parser abbastanza compatto, non sono sicuro che sia davvero adatto per l'alterazione, ma è scritto in Haskell.


2

Lo zoo del linguaggio di programmazione di Andrej Bauer ha una piccola implementazione di un linguaggio di programmazione puramente funzionale chiamato un po 'sfacciatamente "minihaskell". Si tratta di circa 700 linee di OCaml, quindi molto digeribili.

Il sito contiene anche versioni giocattolo dei linguaggi di programmazione in stile ML, in stile Prolog e OO.


1

Non pensi che sarebbe più facile prendere le fonti GHC e togliere quello che non vuoi, piuttosto che scrivere da zero il tuo interprete Haskell? In generale, la rimozione delle funzionalità dovrebbe essere molto minore rispetto alla creazione / aggiunta di funzionalità.

GHC è scritto in Haskell comunque, quindi tecnicamente rimane con la tua domanda di un interprete Haskell scritta in Haskell.

Probabilmente non sarebbe troppo difficile rendere il tutto collegato staticamente e poi distribuire solo il tuo GHCi personalizzato, in modo che gli studenti non possano caricare altri moduli sorgente Haskell. Non ho idea di quanto lavoro ci vorrebbe per impedire loro di caricare altri file oggetto Haskell. Potresti voler disabilitare anche FFI, se hai un sacco di imbroglioni nelle tue classi :)


1
Non è così facile come sembra, poiché molte funzionalità dipendono da altre. Ma forse l'OP vuole solo non importare Prelude e invece fornire il proprio. La maggior parte di Haskell che vedi sono funzioni normali, non caratteristiche specifiche del runtime. (Ma ovviamente molti lo sono .)
jrockway

0

Il motivo per cui ci sono così tanti interpreti LISP è che LISP è fondamentalmente un predecessore di JSON: un semplice formato per codificare i dati. Questo rende la parte frontend abbastanza facile da gestire. In confronto a ciò, Haskell, specialmente con le estensioni del linguaggio, non è il linguaggio più semplice da analizzare. Questi sono alcuni costrutti sintattici che sembrano difficili da ottenere correttamente:

  • operatori con precedenza, associatività e fissità configurabili,
  • commenti annidati
  • regola di layout
  • sintassi del modello
  • do- blocchi e desugaring in codice monadico

Ognuno di questi, tranne forse gli operatori, potrebbe essere affrontato dagli studenti dopo il corso di costruzione del compilatore, ma distoglierebbe l'attenzione da come Haskell funziona effettivamente. In aggiunta a ciò, potresti non voler implementare direttamente tutti i costrutti sintattici di Haskell, ma invece implementare i passaggi per sbarazzartene. Il che ci porta al nocciolo letterale della questione, gioco di parole completamente inteso.

Il mio suggerimento è di implementare il typechecking e un interprete Coreinvece di Haskell completo. Entrambi questi compiti sono già abbastanza complessi da soli. Questo linguaggio, sebbene sia ancora un linguaggio funzionale fortemente tipizzato, è molto meno complicato da gestire in termini di ottimizzazione e generazione di codice. Tuttavia, è ancora indipendente dalla macchina sottostante. Pertanto, GHC lo utilizza come linguaggio intermedio e traduce in esso la maggior parte dei costrutti sintassici di Haskell.

Inoltre, non dovresti evitare di usare il frontend di GHC (o di un altro compilatore). Non lo considererei un inganno poiché i LISP personalizzati utilizzano il parser del sistema LISP host (almeno durante il bootstrap). Pulire gli Coresnippet e presentarli agli studenti, insieme al codice originale, dovrebbe consentire di fornire una panoramica di ciò che fa il frontend e del motivo per cui è preferibile non reimplementarlo.

Di seguito sono riportati alcuni collegamenti alla documentazione di Corecome utilizzato in GHC:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.