Questo è esattamente ciò che il Reader monad è per:
La monade Reader (chiamato anche l'ambiente Monade). Rappresenta un calcolo , che può leggere i valori da un ambiente condiviso, passare i valori dalla funzione alla funzione ed eseguire i calcoli secondari in un ambiente modificato .
Come osserva Sjoerd, la monade dà più potere qui che è necessario, ma è comunque possibile utilizzare la monade Reader per questo problema senza nemmeno digitando do
:
import qualified Data.Map as M
import Control.Applicative ((<$>), (<*>))
import Control.Monad.Reader
data Expr = Const Bool
| Var Char
| Not Expr
| And Expr Expr
| Or Expr Expr
| Xor Expr Expr
Basta mettere il vostro tipo di ambiente come primo argomento per il costruttore del tipo Reader
e il tipo di risultato originale come secondo.
eval' :: Expr -> Reader (M.Map Char Bool) Bool
Invece di c
come il valore della causa Const
, utilizzare return
per sollevare in monade:
eval' (Const c) = return c
Quando è necessario l'ambiente per la ricerca del valore di una variabile, l'uso ask
. Utilizzando do
la notazione, è possibile scrivere il caso Var
in questo modo:
eval' (Var v) = do values <- ask
return (M.findWithDefault False v values)
Penso che sia più bello, però, di utilizzare fmap
aka <$>
:
eval' (Var v) = M.findWithDefault False v <$> ask
Allo stesso modo, l'unario not
può essere fmap
PED over il risultato della ricorsione:
eval' (Not x) = not <$> eval' x
Fina lly, l'istanza Applicative di Reader rende i casi binari piacevole:
eval' (And a b) = (&&) <$> eval' a <*> eval' b
eval' (Or a b) = (||) <$> eval' a <*> eval' b
eval' (Xor a b) = (/=) <$> eval' a <*> eval' b
Poi, per ottenere tutto è cominciato, ecco un aiuto per creare l'ambiente iniziale ed eseguire il calcolo:
eval :: Expr -> [(Char,Bool)] -> Bool
eval exp env = runReader (eval' exp) (M.fromList env)
Non solo è più semplice, probabilmente è anche più efficiente. Credo che si chiami la trasformazione dell'argomento statico. –