Esto es exactamente lo que el Reader monad es para:
La mónada Reader (también llamada la mónada Medio Ambiente). Representa un cálculo , que puede leer valores de un entorno compartido, pasar los valores de la función a la función y ejecutar subcomputaciones en un entorno modificado .
Como notas Sjoerd, la mónada da más poder aquí de lo que necesita, pero todavía se puede utilizar la mónada Reader para este problema sin mucho más que escribir 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
Sólo hay que poner el tipo de entorno como el primer argumento para el constructor de tipo Reader
, y su tipo de resultado original como el segundo.
eval' :: Expr -> Reader (M.Map Char Bool) Bool
En lugar de simplemente c
como el valor de la caja Const
, utilice return
a elevar ésta en la mónada:
eval' (Const c) = return c
Cuando se necesita el entorno para buscar el valor de una variable, el uso ask
. Usando do
notación, se puede escribir el caso Var
así:
eval' (Var v) = do values <- ask
return (M.findWithDefault False v values)
Creo que es más agradable, sin embargo, utilizar fmap
aka <$>
:
eval' (Var v) = M.findWithDefault False v <$> ask
Del mismo modo, el unario not
puede fmap
PED sobre el resultado de la recursión:
eval' (Not x) = not <$> eval' x
Fina lly, la Applicative instancia del lector hace que los casos binarios agradable:
eval' (And a b) = (&&) <$> eval' a <*> eval' b
eval' (Or a b) = (||) <$> eval' a <*> eval' b
eval' (Xor a b) = (/=) <$> eval' a <*> eval' b
Entonces, para conseguir que todo empezó, aquí está un ayudante para crear el entorno inicial y ejecutar el cálculo:
eval :: Expr -> [(Char,Bool)] -> Bool
eval exp env = runReader (eval' exp) (M.fromList env)
No solo es más simple, también es más eficiente. Creo que se llama transformación de argumento estático. –