valutatore reciprocamente ricorsivo in Haskell
-
29-09-2019 - |
Domanda
Aggiornamento: Ho aggiunto una risposta che descrive la mia soluzione definitiva (suggerimento: il tipo di dati singola Expr
non era sufficiente).
scrivendo un valutatore per un po 'di linguaggio delle espressioni, ma mi sono bloccato sulla LetRec
costrutto.
Questa è la lingua:
type Var = String
type Binds = [(Var, Expr)]
data Expr
= Var Var
| Lam Var Expr
| App Expr Expr
| Con Int
| Sub Expr Expr
| If Expr Expr Expr
| Let Var Expr Expr
| LetRec Binds Expr
deriving (Show, Eq)
E questo questo il valutatore finora:
data Value
= ValInt Int
| ValFun Env Var Expr
deriving (Show, Eq)
type Env = [(Var, Value)]
eval :: Env -> Expr -> Either String Value
eval env (Var x) = maybe (throwError $ x ++ " not found")
return
(lookup x env)
eval env (Lam x e) = return $ ValFun env x e
eval env (App e1 e2) = do
v1 <- eval env e1
v2 <- eval env e2
case v1 of
ValFun env1 x e -> eval ((x, v2):env1) e
_ -> throwError "First arg to App not a function"
eval _ (Con x) = return $ ValInt x
eval env (Sub e1 e2) = do
v1 <- eval env e1
v2 <- eval env e2
case (v1, v2) of
(ValInt x, ValInt y) -> return $ ValInt (x - y)
_ -> throwError "Both args to Sub must be ints"
eval env (If p t f) = do
v1 <- eval env p
case v1 of
ValInt x -> if x /= 0
then eval env t
else eval env f
_ -> throwError "First arg of If must be an int"
eval env (Let x e1 e2) = do
v1 <- eval env e1
eval ((x, v1):env) e2
eval env (LetRec bs e) = do
env' <- evalBinds
eval env' e
where
evalBinds = mfix $ \env' -> do
env'' <- mapM (\(x, e') -> eval env' e' >>= \v -> return (x, v)) bs
return $ nub (env'' ++ env)
Questa è la mia funzione di test voglio valutare:
test3 :: Expr
test3 = LetRec [ ("even", Lam "x" (If (Var "x")
(Var "odd" `App` (Var "x" `Sub` Con 1))
(Con 1)
))
, ("odd", Lam "x" (If (Var "x")
(Var "even" `App` (Var "x" `Sub` Con 1))
(Con 0)
))
]
(Var "even" `App` Con 5)
Modifica
In base alla risposta di Travis' e il commento di Luca, ho aggiornato il mio codice di utilizzare la MonadFix istanza per l'Error monade. L'esempio precedente funziona bene ora! Tuttavia, l'esempio soffietto non funziona correttamente:
test4 :: Expr
test4 = LetRec [ ("x", Con 3)
, ("y", Var "x")
]
(Con 0)
Nel valutare questo, il valutatore loop, e non succede nulla. Sto indovinando che ho fatto qualcosa di un po 'troppo severo qui, ma non sono sicuro di cosa si tratta. Am I violando una delle leggi MonadFix?
Soluzione
Quando Haskell lancia un attacco, che di solito è un'indicazione che non avete pensato con chiarezza su una questione fondamentale del problema. In questo caso, la domanda è: quale modello di valutazione vuoi utilizzare per la propria lingua? Chiamata per valore o chiamata per necessità?
Il tuo rappresentazione di ambienti come [(Var,Value)]
suggerisce che si desidera utilizzare call-by-value, dal momento che ogni Expr
viene valutata ad un Value
subito prima di riporla in un ambiente. Ma letrec
non va bene con quello, e il tuo secondo esempio mostra!
Inoltre, nota che il modello di valutazione della lingua del paese ospitante (Haskell) interferirà con il modello di valutazione della lingua che si desidera implementare; in effetti, è quello che si sta facendo uso di per i vostri esempi: nonostante la loro finalità, i suoi Value
s non vengono valutati a forma normale testa debole
Se non avete un quadro chiaro del modello di valutazione del tuo piccolo linguaggio delle espressioni, è non farà molti progressi sulla letrec
o sul controllo degli errori strutture.
Modifica
Per un esempio di specifica letrec
in una lingua chiamata per valore, hanno uno sguardo al Manuale O'Caml . Al livello più semplice, che permettono solo sul lato destro che sono le espressioni lambda, vale a dire le cose che sono noti per essere sintatticamente valori.
Altri suggerimenti
Forse mi manca qualcosa, ma non lo fa il seguente lavoro?
eval env (LetRec bs ex) = eval env' ex
where
env' = env ++ map (\(v, e) -> (v, eval env' e)) bs
Per la vostra versione aggiornata: Che cosa circa il seguente approccio? Funziona come desiderata sul banco di prova, e non buttare via gli errori nelle espressioni LetRec
:
data Value
= ValInt Int
| ValFun EnvWithError Var Expr
deriving (Show, Eq)
type Env = [(Var, Value)]
type EnvWithError = [(Var, Either String Value)]
eval :: Env -> Expr -> Either String Value
eval = eval' . map (second Right)
where
eval' :: EnvWithError -> Expr -> Either String Value
eval' env (Var x) = maybe (throwError $ x ++ " not found")
(join . return)
(lookup x env)
eval' env (Lam x e) = return $ ValFun env x e
eval' env (App e1 e2) = do
v1 <- eval' env e1
v2 <- eval' env e2
case v1 of
ValFun env1 x e -> eval' ((x, Right v2):env1) e
_ -> throwError "First arg to App not a function"
eval' _ (Con x) = return $ ValInt x
eval' env (Sub e1 e2) = do
v1 <- eval' env e1
v2 <- eval' env e2
case (v1, v2) of
(ValInt x, ValInt y) -> return $ ValInt (x - y)
_ -> throwError "Both args to Sub must be ints"
eval' env (If p t f) = do
v1 <- eval' env p
case v1 of
ValInt x -> if x /= 0
then eval' env t
else eval' env f
_ -> throwError "First arg of If must be an int"
eval' env (Let x e1 e2) = do
v1 <- eval' env e1
eval' ((x, Right v1):env) e2
eval' env (LetRec bs ex) = eval' env' ex
where
env' = env ++ map (\(v, e) -> (v, eval' env' e)) bs
Rispondendo alla mia domanda; Volevo condividere la soluzione finale mi è venuta.
Come Heinrich correttamente sottolineato , non l'ho fatto davvero pensare attraverso l'impatto l'ordine di valutazione ha.
In un linguaggio rigoroso (call-by-value), un'espressione che è già un valore (forma normale testa debole) è diverso da un'espressione che necessita ancora qualche valutazione. Una volta che ho codificato questa distinzione nel mio tipo di dati, tutto è andato a posto:
type Var = String
type Binds = [(Var, Val)]
data Val
= Con Int
| Lam Var Expr
deriving (Show, Eq)
data Expr
= Val Val
| Var Var
| App Expr Expr
| Sub Expr Expr
| If Expr Expr Expr
| Let Var Expr Expr
| LetRec Binds Expr
deriving (Show, Eq)
L'unica differenza con il mio il mio tipo di dati Expr
originale, è che ho tirato fuori due costruttori (Con
e Lam
) nel proprio tipo di dati Val
. Il tipo di dati Expr
ha un nuovo Val
costruttore, questo rappresenta il fatto che un valore è anche un'espressione valida.
Con valori nel loro tipo di dati, possono essere manipolati separatamente dagli altri espressione, per esempio, attacchi letrec
può contenere solo i valori, non altre espressioni.
Questa distinzione è fatta anche in altre lingue severe come il C, dove solo le funzioni e le costanti possono essere definite in ambito globale.
completa codice per la funzione di valutatore aggiornato.