Это именно то, для чего предназначена Монада Reader :
Монада Reader (также называемая монадой Environment). Представляет
вычисления, которые могут читать значения из общей среды, проходят
значения от функции к функции, и выполнить подвычисления в
модифицированная среда.
Как отмечает Sjoerd, монада дает здесь больше энергии, чем вам нужно, но вы все равно можете использовать монаду Reader для этой задачи, даже не набрав 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
Просто поместите ваш тип среды в качестве первого аргумента в конструктор типа Reader
, а исходный тип результата в качестве второго.
eval' :: Expr -> Reader (M.Map Char Bool) Bool
Вместо c
в качестве значения Const
, используйте return
, чтобы поднять его в монаду:
eval' (Const c) = return c
Если вам нужна среда для поиска значения переменной, используйте ask
. Используя запись do
, вы можете написать регистр Var
следующим образом:
eval' (Var v) = do values <- ask
return (M.findWithDefault False v values)
Я думаю, что лучше использовать fmap
a.k.a. <$>
:
eval' (Var v) = M.findWithDefault False v <$> ask
Аналогично, унарный not
может быть fmap
ped по результату рекурсии:
eval' (Not x) = not <$> eval' x
Наконец, Applicative экземпляр Reader делает двоичные случаи приятными:
eval' (And a b) = (&&) <$> eval' a <*> eval' b
eval' (Or a b) = (||) <$> eval' a <*> eval' b
eval' (Xor a b) = (/=) <$> eval' a <*> eval' b
Затем, чтобы начать все это, вот помощник для создания начальной среды и выполнения вычислений:
eval :: Expr -> [(Char,Bool)] -> Bool
eval exp env = runReader (eval' exp) (M.fromList env)