【问题标题】:Unwrap value from Monad state context and concatenate two state contexts从 Monad 状态上下文中解包值并连接两个状态上下文
【发布时间】:2018-12-10 10:52:18
【问题描述】:

我正在尝试为我的简单命令式语言解析器编写一个 eval 函数,但是当我使用 Control.MonadState 编写它时遇到了一些问题>.

evalComms Let case 我需要解开类型 (m Int) 以传递 Int 来更新函数,有吗有办法吗?

同样在evalComms 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

【问题讨论】:

  • 我仍然是 Haskell 的初学者,所以我没有完全遵循您的代码 - 但关于“我需要解开类型 (m Int) 以仅传递 Int 来更新函数,有没有办法做到这一点?”,这正是一元“绑定”运算符 (&gt;&gt;=) 的设计初衷吗?
  • @RobinZigmond 我尝试使用它,但我无法让它工作!我会再尝试。如果你能提供一个例子,或者如果其他人可以,那就太好了
  • @victor.ja (&gt;&gt;=) 绝对是这项工作的正确工具,所以请展示您尝试过的内容以及遇到的错误。
  • 这样做:return (liftM2 (:) (evalComm c2) (evalComm c1)) 编译? evalComm c1 不是list,而是m ()
  • @LuisMorillo 不,它没有。我澄清了 evalComm 是错误的。也许我可以把它加粗,因为它在问题的中间!

标签: parsing haskell functional-programming monads


【解决方案1】:

让案例

正如 Zigmond 和 Wagner 所说,(&gt;&gt;=) 是完成这项工作的正确工具。让我们看看类型:

(>>=) :: m a -> (a -> m b) -> m b
update :: Variable -> Int -> m ()
evalIntExp i :: m Int
v :: Variable

你能想出一种方法将它们组合成预期的类型m ()吗?请记住,您可以部分应用函数来取回参数较少的函数。

Seq 案例

让我们再看看类型。

我们有两个m () 类型的值(evalComm c1evalComm c2)并希望将它们组合成一个m () 类型的值。我们可以通过创建一个忽略其参数的函数来再次使用&gt;&gt;=

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 = () 可以使用,但与仅使用&gt;&gt; 相比,它是不必要的复杂。但是,我鼓励您尝试将其作为练习。 () -&gt; () -&gt; () 类型的函数看起来如何?

return :: a -> m a

当你有一个纯值并且需要将其转换为一元值时使用它,所以这里不需要使用它。结果将具有双重包装类型m (m ()),这不是您想要的。

最后的话

如您所见,这些类型在编写 Haskell 程序时非常有用。每当您想知道可以组合哪些东西时,请查看类型。您可以通过在 GHCi 中输入 :t &lt;expression&gt; 来查看表达式的类型。

【讨论】:

    【解决方案2】:

    两件事。

    首先,我认为你的更新功能是错误的,因为你模式匹配同一件事两次。为什么不呢?:

    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 发布的那样,使用&gt;&gt; 运算符将执行以下操作:计算第一个结果,然后计算第二个。在这种情况下,使用evalComm 计算结果最终更新状态或返回()。所以你的代码应该是这样的:

    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 &gt;&gt;= \o -&gt; update v o 表示:计算evalIntExp i,将生成的Int 传递给update 函数

    这个实现返回:

    let exp1 = Seq (Seq (Let "string1" (Const 1)) (Let "string2" (Const 2))) Skip 
    
    > eval exp1
    [("string1",1),("string2",2)]
    

    但在其他示例中失败。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-15
      • 1970-01-01
      • 2011-05-29
      • 2021-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      相关资源
      最近更新 更多