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 ..)。您应该使用 >>= 运算符。此外,您正在尝试组合两个单子效应:ReaderMaybe。 "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 (->) rid 而不是 Reader rask,你会对此有更好的感觉。另一方面,对于 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