Haskell,使用 monad Reader 的简单表达式求值器
Haskell, simple evaluator of expressions with monad Reader
我确实写了一些有效的表达式求值器。但是,有时我会遇到异常:
*** Exception: Maybe.fromJust: Nothing
我知道是什么了。但是,我无法真正解决它。在这种情况下,我的目标是 return Nothing
。
你能帮我吗?
type Var = String
data Exp = EInt Int
| EOp Op Exp Exp
| EVar Var
| ELet Var Exp Exp -- let var = e1 in e2
data Op = OpAdd | OpMul | OpSub
evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int)
evalExpM (EInt n) = return $ Just n
evalExpM (EVar var) = ask >>= (\x -> return ( Map.lookup var x))
evalExpM (EOp OpAdd e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) + (fromJust (runReader (evalExpM e2) x )))))
evalExpM (EOp OpMul e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) * (fromJust (runReader (evalExpM e2) x )))))
evalExpM (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) - (fromJust (runReader (evalExpM e2) x )))))
evalExpM (ELet var e1 e2) = ask >>= (\x -> (local (Map.insert var (fromJust (runReader (evalExpM e1) x))) (evalExpM e2)) >>= (\y -> return y))
evalExp :: Exp -> Int
evalExp exp = fromJust $ runReader (evalExpM exp) Map.empty
首先,使用 monadic 计算的方法不是每次遇到包含在 monad 中的值时 "run" 计算(在本例中为 Reader ..
)。您应该使用 >>=
运算符。此外,您正在尝试组合两个单子效应:Reader
和 Maybe
。 "standard" 方法是使用 monad 转换器,但幸运的是 Reader
(或更准确地说 ReaderT
)本身就是一个 monad 转换器。
此外,您将从一些抽象中受益,即:
import Control.Monad.Reader
import qualified Data.Map as M
type Env = M.Map String Int
type EvalM = ReaderT (M.Map String Int) Maybe
lookupEnv :: String -> EvalM Int
lookupEnv x = ask >>= lift . M.lookup x
withBind :: String -> Int -> EvalM a -> EvalM a
withBind x v = local (M.insert x v)
这些函数定义了一个接口,用于处理查找可能会失败的环境。您应该一次编写这些函数,而不是在需要时内联它们的定义。现在你的函数的基本情况是微不足道的:
evalExpM :: Exp -> EvalM Int
evalExpM (EInt n) = return n
evalExpM (EVar v) = lookupEnv v
许多人(可能包括我自己)会在递归情况下使用应用运算符,但您可以避免符号汤:
evalExpM (EOp op e1 e2) = liftM2
(case op of
OpAdd -> (+)
OpMul -> (-)
OpSub -> (-)
) (evalExpM e1) (evalExpM e2)
evalExpM (ELet var e1 e2) = evalExpM e1 >>= \e -> withBind var e $ evalExpM e2
运行 它和以前一样 - 只需将 runReader
更改为 runReaderT
- 但现在你只需要 运行 当你真正完成计算时上下文。
evalExp :: Exp -> Int
evalExp e = maybe (error "evalExp") id $ runReaderT (evalExpM e) M.empty
您打算使用 Monad Maybe
,它会自动向下传递 Nothing
,因此您无需手动处理。
因此,您可以使用类型 (ReaderT r Maybe)(a)
而不是 (Reader r)(Maybe a)
。
但我猜你最终想将其简化为 r -> Maybe a
。
无论如何,您正在使用 Monad Reader r
,这只是提供参数的一种奇特方式。
因为 r->a
只是 (->) r a
的语法糖,而 (->) r
已经是一个 Reader Monad,你可以用它来代替:你甚至可以用 [= 替换 ask
22=].
您可能不想一直使用愚蠢的 ask
功能,因此您可以使用 reader
来简单地提升功能。
evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int)
evalExpM (EVar var) = ask >>= (\x -> lift ( Map.lookup var x))
evalExpM (EVar var) = reader $ \x -> Map.lookup var x
evalExpM (EVar var) = reader $ Map.lookup var
只有当你想自动将参数传递给内部函数时,才使用Reader
(或ReaderT
)更方便。但即便如此,您也可以只使用 Monad (->) r
或简单地传递一个参数。如果你总是使用 Monad (->) r
和 id
而不是 Reader r
和 ask
,你会对此有更好的感觉。另一方面,对于 ReaderT
,当您必须将一个参数传递给以 do
表示法使用的大多数函数时,您会看到它的需要。
evalExp :: Exp -> Map.Map String Int -> Maybe Int
evalExp (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x )))))
evalExp (EOp OpSub e1 e2) = do -- Monad ((->) (Map.Map String Int))
x <- id -- hehe, same as ask
return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x ))))
evalExp (EOp OpSub e1 e2) x = -- directly with param x
(Just ((fromJust ((evalExp e1) x)) - (fromJust ((evalExp e2) x ))))
evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe)
let a = fromJust ((evalExp e1) x)
let b = fromJust ((evalExp e2) x)
Just $ a - b
evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe)
a <- return $ fromJust ((evalExp e1) x)
b <- return $ fromJust ((evalExp e2) x)
Just $ a - b
-- and without that bug:
evalExp (EOp OpSub e1 e2) x = do -- because return=Just
a <- evalExp e1 x
b <- evalExp e2 x
return $ a - b
evalExp (EOp OpSub e1 e2) = runReaderT $ do -- Monad (ReaderT (Map...) Maybe)
a <- ReaderT $ evalExp e1
b <- ReaderT $ evalExp e2 -- this is a nice example to use ReaderT
return $ a - b
-- this is ugly unless we need that param wrapped quite often:
evalExpM :: Exp -> ReaderT (Map.Map String Int) Maybe Int
evalExpM exp = ReaderT $ evalExp exp
我确实写了一些有效的表达式求值器。但是,有时我会遇到异常:
*** Exception: Maybe.fromJust: Nothing
我知道是什么了。但是,我无法真正解决它。在这种情况下,我的目标是 return Nothing
。
你能帮我吗?
type Var = String
data Exp = EInt Int
| EOp Op Exp Exp
| EVar Var
| ELet Var Exp Exp -- let var = e1 in e2
data Op = OpAdd | OpMul | OpSub
evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int)
evalExpM (EInt n) = return $ Just n
evalExpM (EVar var) = ask >>= (\x -> return ( Map.lookup var x))
evalExpM (EOp OpAdd e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) + (fromJust (runReader (evalExpM e2) x )))))
evalExpM (EOp OpMul e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) * (fromJust (runReader (evalExpM e2) x )))))
evalExpM (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) - (fromJust (runReader (evalExpM e2) x )))))
evalExpM (ELet var e1 e2) = ask >>= (\x -> (local (Map.insert var (fromJust (runReader (evalExpM e1) x))) (evalExpM e2)) >>= (\y -> return y))
evalExp :: Exp -> Int
evalExp exp = fromJust $ runReader (evalExpM exp) Map.empty
首先,使用 monadic 计算的方法不是每次遇到包含在 monad 中的值时 "run" 计算(在本例中为 Reader ..
)。您应该使用 >>=
运算符。此外,您正在尝试组合两个单子效应:Reader
和 Maybe
。 "standard" 方法是使用 monad 转换器,但幸运的是 Reader
(或更准确地说 ReaderT
)本身就是一个 monad 转换器。
此外,您将从一些抽象中受益,即:
import Control.Monad.Reader
import qualified Data.Map as M
type Env = M.Map String Int
type EvalM = ReaderT (M.Map String Int) Maybe
lookupEnv :: String -> EvalM Int
lookupEnv x = ask >>= lift . M.lookup x
withBind :: String -> Int -> EvalM a -> EvalM a
withBind x v = local (M.insert x v)
这些函数定义了一个接口,用于处理查找可能会失败的环境。您应该一次编写这些函数,而不是在需要时内联它们的定义。现在你的函数的基本情况是微不足道的:
evalExpM :: Exp -> EvalM Int
evalExpM (EInt n) = return n
evalExpM (EVar v) = lookupEnv v
许多人(可能包括我自己)会在递归情况下使用应用运算符,但您可以避免符号汤:
evalExpM (EOp op e1 e2) = liftM2
(case op of
OpAdd -> (+)
OpMul -> (-)
OpSub -> (-)
) (evalExpM e1) (evalExpM e2)
evalExpM (ELet var e1 e2) = evalExpM e1 >>= \e -> withBind var e $ evalExpM e2
运行 它和以前一样 - 只需将 runReader
更改为 runReaderT
- 但现在你只需要 运行 当你真正完成计算时上下文。
evalExp :: Exp -> Int
evalExp e = maybe (error "evalExp") id $ runReaderT (evalExpM e) M.empty
您打算使用 Monad Maybe
,它会自动向下传递 Nothing
,因此您无需手动处理。
因此,您可以使用类型 (ReaderT r Maybe)(a)
而不是 (Reader r)(Maybe a)
。
但我猜你最终想将其简化为 r -> Maybe a
。
无论如何,您正在使用 Monad Reader r
,这只是提供参数的一种奇特方式。
因为 r->a
只是 (->) r a
的语法糖,而 (->) r
已经是一个 Reader Monad,你可以用它来代替:你甚至可以用 [= 替换 ask
22=].
您可能不想一直使用愚蠢的 ask
功能,因此您可以使用 reader
来简单地提升功能。
evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int)
evalExpM (EVar var) = ask >>= (\x -> lift ( Map.lookup var x))
evalExpM (EVar var) = reader $ \x -> Map.lookup var x
evalExpM (EVar var) = reader $ Map.lookup var
只有当你想自动将参数传递给内部函数时,才使用Reader
(或ReaderT
)更方便。但即便如此,您也可以只使用 Monad (->) r
或简单地传递一个参数。如果你总是使用 Monad (->) r
和 id
而不是 Reader r
和 ask
,你会对此有更好的感觉。另一方面,对于 ReaderT
,当您必须将一个参数传递给以 do
表示法使用的大多数函数时,您会看到它的需要。
evalExp :: Exp -> Map.Map String Int -> Maybe Int
evalExp (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x )))))
evalExp (EOp OpSub e1 e2) = do -- Monad ((->) (Map.Map String Int))
x <- id -- hehe, same as ask
return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x ))))
evalExp (EOp OpSub e1 e2) x = -- directly with param x
(Just ((fromJust ((evalExp e1) x)) - (fromJust ((evalExp e2) x ))))
evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe)
let a = fromJust ((evalExp e1) x)
let b = fromJust ((evalExp e2) x)
Just $ a - b
evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe)
a <- return $ fromJust ((evalExp e1) x)
b <- return $ fromJust ((evalExp e2) x)
Just $ a - b
-- and without that bug:
evalExp (EOp OpSub e1 e2) x = do -- because return=Just
a <- evalExp e1 x
b <- evalExp e2 x
return $ a - b
evalExp (EOp OpSub e1 e2) = runReaderT $ do -- Monad (ReaderT (Map...) Maybe)
a <- ReaderT $ evalExp e1
b <- ReaderT $ evalExp e2 -- this is a nice example to use ReaderT
return $ a - b
-- this is ugly unless we need that param wrapped quite often:
evalExpM :: Exp -> ReaderT (Map.Map String Int) Maybe Int
evalExpM exp = ReaderT $ evalExp exp