从 Monad 状态上下文中解包值并连接两个状态上下文
Unwrap value from Monad state context and concatenate two state contexts
我正在尝试为我的简单命令式语言解析器编写一个 eval
函数,但是当我使用 Control.Monad[=32= 编写它时遇到了一些问题] 和 州.
在 evalComm
s 让 case 我需要解包类型 (m Int) 以传递 Int更新功能,有什么办法吗?
同样在 evalComm
s Seq case ,当递归以两种方式打开时,我需要连接两个 evalComm 函数。 liftM
是这种情况的替代方案吗?
type Env = [(Variable,Int)]
initState :: Env
initState = []
newtype State a = State { runState :: Env -> (a, Env) }
instance Monad State where
return x = State (\s -> (x, s))
m >>= f = State (\s -> let (v, s') = runState m s in
runState (f v) s')
instance Functor State where
fmap = liftM
instance Applicative State where
pure = return
(<*>) = ap
class Monad m => MonadState m where
lookfor :: Variable -> m Int
update :: Variable -> Int -> m ()
instance MonadState State where
lookfor v = State (\s -> (lookfor' v s, s))
where lookfor' v ((u, j):ss) | v == u = j
| v /= u = lookfor' v ss
update v i = State (\s -> ((), update' v i s))
where update' v i [] = [(v, i)]
update' v i ((u, _):ss) | v == u = (v, i):ss
update' v i ((u, j):ss) | v /= u = (u, j):(update' v i ss)
eval :: Comm -> Env
eval p = snd (runState (evalComm p) initState)
evalComm :: MonadState m => Comm -> m ()
evalComm c = case c of
Skip -> return ()
Let v i -> update v (evalIntExp i)
Seq c1 c2 -> return (liftM2 (:) (evalComm c2) (evalComm c1))
evalIntExp :: MonadState m => IntExp -> m Int
evalIntExp v = case v of
Const x -> return (fromInteger x)
Var x -> lookfor x
UMinus x -> liftM (*(-1)) (evalIntExp x)
Plus x y -> liftM2 (+) (evalIntExp x) (evalIntExp y)
Minus x y -> liftM2 (-) (evalIntExp x) (evalIntExp y)
Times x y -> liftM2 (*) (evalIntExp x) (evalIntExp y)
Div x y -> liftM2 div (evalIntExp x) (evalIntExp y)
evalBoolExp :: MonadState m => BoolExp -> m Bool
evalBoolExp b = case b of
BTrue -> return True
BFalse -> return False
Eq x y -> liftM2 (==) (evalIntExp x) (evalIntExp y)
Lt x y -> liftM2 (<) (evalIntExp x) (evalIntExp y)
Gt x y -> liftM2 (>) (evalIntExp x) (evalIntExp y)
And b0 b1 -> liftM2 (&&) (evalBoolExp b0) (evalBoolExp b1)
Or b0 b1 -> liftM2 (||) (evalBoolExp b0) (evalBoolExp b1)
Not b -> liftM not (evalBoolExp b)
请注意,evalComm 的代码无法正常工作,它可能不正确。
这是我的抽象语法树:
type Variable = String
data IntExp = Const Integer
| Var Variable
| UMinus IntExp
| Plus IntExp IntExp
| Minus IntExp IntExp
| Times IntExp IntExp
| Div IntExp IntExp
| Quest BoolExp IntExp IntExp
deriving Show
data BoolExp = BTrue
| BFalse
| Eq IntExp IntExp
| Lt IntExp IntExp
| Gt IntExp IntExp
| And BoolExp BoolExp
| Or BoolExp BoolExp
| Not BoolExp
deriving Show
data Comm = Skip
| Let Variable IntExp
| Seq Comm Comm
| Cond BoolExp Comm Comm
| While BoolExp Comm
| Repeat Comm BoolExp
deriving Show
Let 案例
正如 Zigmond 和 Wagner 所说,(>>=)
是完成这项工作的正确工具。让我们看看类型:
(>>=) :: m a -> (a -> m b) -> m b
update :: Variable -> Int -> m ()
evalIntExp i :: m Int
v :: Variable
你能想办法把这些组合成预期的类型吗m ()
?请记住,您可以部分应用函数以取回参数较少的函数。
Seq 案例
我们再来看看类型。
我们有两个 m ()
类型的值,(evalComm c1
和 evalComm c2
),我们想将它们组合成一个 m ()
类型的值。我们可以通过创建一个忽略其参数的函数来再次使用 >>=
:
Seq c1 c2 -> (evalComm c1) >>= (\x -> (evalComm c1))
但是,这种情况很常见,因此已经有一个内置函数:
(>>) :: m a -> m b -> m b
Seq c1 c2 -> evalComm c1 >> evalComm c1
让我们看看你之前的代码
liftM2 (:) :: m a -> m [a] -> m [a]
您没有列表,所以这没有用。
liftM2 :: (a -> b -> c) -> m a -> m b -> m c
如果 a = b = c = ()
可以使用,但与仅使用 >>
相比,它不必要地复杂。但是,我鼓励您尝试将其作为练习。 () -> () -> ()
类型的函数看起来如何?
return :: a -> m a
当你有一个纯值,需要将它转换成一个monadic值时使用这个,所以这里不需要使用它。结果将具有双重包装类型 m (m ())
,这不是您想要的。
最后的话
如您所见,类型在编写 Haskell 程序时非常有用。每当您想知道可以组合什么东西时,请查看类型。您可以通过在 GHCi 中输入 :t <expression>
来检查表达式的类型。
两件事。
首先,我认为你的更新函数是错误的,因为你对同一事物进行了两次模式匹配。为什么不呢?:
update v i = State (\s -> ((), update' v i s))
where update' v i [] = [(v, i)]
update' v i ((u, j):ss) | v == u = (v, i):ss
| v /= u = (u, j):(update' v i ss)
此更新函数正在创建一个配对列表。正如@Hjulle 发布的那样,使用 >>
运算符将执行: 计算第一个结果,然后计算第二个 。在这种情况下,使用 evalComm
计算结果最终是 更新状态或 return ()
。所以你的代码应该是这样的:
evalComm :: MonadState m => Comm -> m ()
evalComm c = case c of
Skip -> return ()
Let v i -> evalIntExp i >>= \o -> update v o
Seq c1 c2 -> evalComm c1 >> evalComm c2
evalIntExp i >>= \o -> update v o
表示:计算evalIntExp i
,将结果Int
传递给update
函数
这个实现 returns:
let exp1 = Seq (Seq (Let "string1" (Const 1)) (Let "string2" (Const 2))) Skip
> eval exp1
[("string1",1),("string2",2)]
但在其他例子中失败了。
我正在尝试为我的简单命令式语言解析器编写一个 eval
函数,但是当我使用 Control.Monad[=32= 编写它时遇到了一些问题] 和 州.
在 evalComm
s 让 case 我需要解包类型 (m Int) 以传递 Int更新功能,有什么办法吗?
同样在 evalComm
s Seq case ,当递归以两种方式打开时,我需要连接两个 evalComm 函数。 liftM
是这种情况的替代方案吗?
type Env = [(Variable,Int)]
initState :: Env
initState = []
newtype State a = State { runState :: Env -> (a, Env) }
instance Monad State where
return x = State (\s -> (x, s))
m >>= f = State (\s -> let (v, s') = runState m s in
runState (f v) s')
instance Functor State where
fmap = liftM
instance Applicative State where
pure = return
(<*>) = ap
class Monad m => MonadState m where
lookfor :: Variable -> m Int
update :: Variable -> Int -> m ()
instance MonadState State where
lookfor v = State (\s -> (lookfor' v s, s))
where lookfor' v ((u, j):ss) | v == u = j
| v /= u = lookfor' v ss
update v i = State (\s -> ((), update' v i s))
where update' v i [] = [(v, i)]
update' v i ((u, _):ss) | v == u = (v, i):ss
update' v i ((u, j):ss) | v /= u = (u, j):(update' v i ss)
eval :: Comm -> Env
eval p = snd (runState (evalComm p) initState)
evalComm :: MonadState m => Comm -> m ()
evalComm c = case c of
Skip -> return ()
Let v i -> update v (evalIntExp i)
Seq c1 c2 -> return (liftM2 (:) (evalComm c2) (evalComm c1))
evalIntExp :: MonadState m => IntExp -> m Int
evalIntExp v = case v of
Const x -> return (fromInteger x)
Var x -> lookfor x
UMinus x -> liftM (*(-1)) (evalIntExp x)
Plus x y -> liftM2 (+) (evalIntExp x) (evalIntExp y)
Minus x y -> liftM2 (-) (evalIntExp x) (evalIntExp y)
Times x y -> liftM2 (*) (evalIntExp x) (evalIntExp y)
Div x y -> liftM2 div (evalIntExp x) (evalIntExp y)
evalBoolExp :: MonadState m => BoolExp -> m Bool
evalBoolExp b = case b of
BTrue -> return True
BFalse -> return False
Eq x y -> liftM2 (==) (evalIntExp x) (evalIntExp y)
Lt x y -> liftM2 (<) (evalIntExp x) (evalIntExp y)
Gt x y -> liftM2 (>) (evalIntExp x) (evalIntExp y)
And b0 b1 -> liftM2 (&&) (evalBoolExp b0) (evalBoolExp b1)
Or b0 b1 -> liftM2 (||) (evalBoolExp b0) (evalBoolExp b1)
Not b -> liftM not (evalBoolExp b)
请注意,evalComm 的代码无法正常工作,它可能不正确。
这是我的抽象语法树:
type Variable = String
data IntExp = Const Integer
| Var Variable
| UMinus IntExp
| Plus IntExp IntExp
| Minus IntExp IntExp
| Times IntExp IntExp
| Div IntExp IntExp
| Quest BoolExp IntExp IntExp
deriving Show
data BoolExp = BTrue
| BFalse
| Eq IntExp IntExp
| Lt IntExp IntExp
| Gt IntExp IntExp
| And BoolExp BoolExp
| Or BoolExp BoolExp
| Not BoolExp
deriving Show
data Comm = Skip
| Let Variable IntExp
| Seq Comm Comm
| Cond BoolExp Comm Comm
| While BoolExp Comm
| Repeat Comm BoolExp
deriving Show
Let 案例
正如 Zigmond 和 Wagner 所说,(>>=)
是完成这项工作的正确工具。让我们看看类型:
(>>=) :: m a -> (a -> m b) -> m b
update :: Variable -> Int -> m ()
evalIntExp i :: m Int
v :: Variable
你能想办法把这些组合成预期的类型吗m ()
?请记住,您可以部分应用函数以取回参数较少的函数。
Seq 案例
我们再来看看类型。
我们有两个 m ()
类型的值,(evalComm c1
和 evalComm c2
),我们想将它们组合成一个 m ()
类型的值。我们可以通过创建一个忽略其参数的函数来再次使用 >>=
:
Seq c1 c2 -> (evalComm c1) >>= (\x -> (evalComm c1))
但是,这种情况很常见,因此已经有一个内置函数:
(>>) :: m a -> m b -> m b
Seq c1 c2 -> evalComm c1 >> evalComm c1
让我们看看你之前的代码
liftM2 (:) :: m a -> m [a] -> m [a]
您没有列表,所以这没有用。
liftM2 :: (a -> b -> c) -> m a -> m b -> m c
如果 a = b = c = ()
可以使用,但与仅使用 >>
相比,它不必要地复杂。但是,我鼓励您尝试将其作为练习。 () -> () -> ()
类型的函数看起来如何?
return :: a -> m a
当你有一个纯值,需要将它转换成一个monadic值时使用这个,所以这里不需要使用它。结果将具有双重包装类型 m (m ())
,这不是您想要的。
最后的话
如您所见,类型在编写 Haskell 程序时非常有用。每当您想知道可以组合什么东西时,请查看类型。您可以通过在 GHCi 中输入 :t <expression>
来检查表达式的类型。
两件事。
首先,我认为你的更新函数是错误的,因为你对同一事物进行了两次模式匹配。为什么不呢?:
update v i = State (\s -> ((), update' v i s))
where update' v i [] = [(v, i)]
update' v i ((u, j):ss) | v == u = (v, i):ss
| v /= u = (u, j):(update' v i ss)
此更新函数正在创建一个配对列表。正如@Hjulle 发布的那样,使用 >>
运算符将执行: 计算第一个结果,然后计算第二个 。在这种情况下,使用 evalComm
计算结果最终是 更新状态或 return ()
。所以你的代码应该是这样的:
evalComm :: MonadState m => Comm -> m ()
evalComm c = case c of
Skip -> return ()
Let v i -> evalIntExp i >>= \o -> update v o
Seq c1 c2 -> evalComm c1 >> evalComm c2
evalIntExp i >>= \o -> update v o
表示:计算evalIntExp i
,将结果Int
传递给update
函数
这个实现 returns:
let exp1 = Seq (Seq (Let "string1" (Const 1)) (Let "string2" (Const 2))) Skip
> eval exp1
[("string1",1),("string2",2)]
但在其他例子中失败了。