关于“了解你的Haskell”的State Monad代码的困惑

时间:2012-04-19 14:42:15

标签: haskell monads state-monad

我正在尝试使用在线图书Learn you a Haskell for great Good来掌握Haskell。

据我所知,到目前为止,我已经能够理解Monads,直到我介绍了State Monad的章节。

但是,提出并声称是Monad实现的State类型代码(我无法在Hoogle中找到它)似乎对我来说太过分了。

  • 首先,我不明白它背后的逻辑,即为什么它应该起作用以及作者如何考虑这种技术。(可能会提出相关文章或白皮书?)

  • 在第4行,建议函数f取1个参数 然而,几行下来我们会看到pop,它不带任何参数!

  • 为了扩展第1点,作者试图使用函数来表示状态。

非常感谢任何帮助理解正在发生的事情。

修改

可能涉及的人,

以下答案彻底涵盖了我的问题 我想补充一点:

在阅读下面建议的文章后,我找到了上面第二点的答案: 一直以来,我假设将使用pop函数,如:    stuff >>= pop因为在绑定类型中第二个参数是函数,而正确的用法是这个pop >>= stuff,我在再次阅读后如何将do-notation转换为plain bind-lambdas时意识到。

4 个答案:

答案 0 :(得分:20)

State monad表示有状态计算,即使用某些外部状态的值并可能修改某些外部状态的计算。当您对有状态计算进行排序时,后面的计算可能会给出不同的结果,具体取决于先前的计算如何修改状态。

由于Haskell中的函数必须是纯的(即没有副作用),我们通过要求每个函数都使用表示世界当前状态的附加参数来模拟外部状态的影响,并返回一个附加值,表示修改后的州。实际上,外部状态是通过一系列计算进行的,就像我刚刚在MSPaint中绘制的图的可憎一样:

enter image description here

注意每个框(表示计算)如何有一个输入和两个输出。

如果查看Monad的{​​{1}}实例,您会发现State的定义告诉您如何执行此线程。它表示要将有状态计算(>>=)绑定到获取有状态计算结果并返回另一个有状态计算的函数c0,我们执行以下操作:

  1. 使用初始状态f运行c0以获得结果和新状态:s0
  2. (val, s1)提供给函数val以获取新的有状态计算,f
  3. 使用修改后的状态c1
  4. 运行新计算c1

    这如何与已经采用s1参数的函数一起使用?因为默认情况下Haskell中的每个函数都是curry,所以我们只是将一个额外的参数(对于状态)添加到结尾,而不是正常的返回值,函数现在返回一个第二个元素是新修改状态的对。而不是

    n

    我们现在有

    f :: a -> b
    

    您可以选择将其视为

    f :: a -> s -> (b, s)
    

    在Haskell中是相同的(因为函数组合是右关联的),它读取“f :: a -> ( s -> (b, s) ) 是一个接受类型f的参数并返回有状态计算的函数”。这就是a monad的全部内容。

答案 1 :(得分:14)

简答:

  1. State旨在利用monad'功能,以模拟具有局部变量的命令式系统状态。基本的想法是在monad中隐藏接受当前状态的活动,并在每一步返回新状态和中间结果(这里我们有s -> (a,s)
  2. 不要将任意函数与State中包含的函数混淆。前者可能有你想要的任何类型(前提是如果你想在状态monad中使用它们,它们最终会产生一些State a)。后者包含s -> (a,s)类型的函数:这​​是由monad管理的状态传递层。
  3. 正如我所说,State中包含的函数实际上是通过(>>=)return生成的,因为它们是为Monad (State s)实例定义的。它的作用是通过代码调用来传递状态。
  4. 第3点也是状态参数从状态monad中实际使用的函数中消失的原因。

    答案很长:

    State Monad已经在不同的论文中进行了研究,并且也存在于Haskell框架中(我现在不记得好的参考文献,我会尽快添加它们)。

    这是它的想法:考虑一个类型data MyState = ...,其值保存系统的当前状态。

    如果你想通过一堆函数传递它,你应该以这样的方式编写每个函数,使它至少将当前状态作为参数并返回一对其结果(取决于状态和其他输入参数)和新的(可能修改的)状态。嗯,这正是州monad告诉你的类型:s -> (a, s)。在我们的示例中,sMyState,旨在传递系统状态。

    包含在State中的函数不接受当前状态以外的参数,因此需要生成新状态和中间结果。您在示例中看到的具有更多参数的函数不是问题,因为当您在monad中的do - 符号中使用它们时,您将它们应用于所有" extra&# 34;需要的参数,意味着它们中的每一个都将导致部分应用的函数,其唯一的剩余参数是状态; State的monad实例将完成其余的工作。

    如果你看一下可能在monad中使用的函数的类型(实际上,在monad中它们通常被称为动作),你会看到它们的结果类型是在monad中装箱:这是这一点告诉你,一旦你给他们所有参数,他们实际上不会返回结果,但(在这种情况下)一个函数s -> (a,s)将符合monad的组成法则。< / p>

    计算将通过将整个块/组合传递给系统的第一个/初始状态来执行。

    最后,不带参数的函数会有类似State a的类型,其中a是它们的返回类型:如果你看一下State的值构造函数,你就是&# 39;再次看到这实际上是一个函数s -> (a,s)

答案 2 :(得分:5)

State monad基本上是

type State s a = s -> (a,s)

从一个状态(s)到一对期望结果(a)和新状态的函数。该实现使得状态的线程隐式并为您处理状态传递和更新,因此不存在意外将错误状态传递给下一个函数的风险。

因此,k > 0 monad中带有State s个参数(其中一个是状态并返回一对内容和一个新状态)的函数成为一个带k-1个参数的函数并返回一个monadic动作(这基本上是一个函数,它采用一个参数,状态,这里)。

在非状态设置中,pop接受一个参数,即堆栈,即状态。因此,在monadic设置中,pop变为State Stack Int操作,没有明确的参数。

使用State monad而不是显式状态传递可以使代码更清晰,错误机会更少,这就是State monad完成的事情。一切都可以在没有它的情况下完成,它会更加麻烦且容易出错。

答案 3 :(得分:3)

我是Haskell的全新手,我也无法理解那本书中的State Monad代码。但是,让我在这里添加我的答案,以帮助将来的某个人。

数目:

  • 他们试图用State Monad完成什么?

    编写处理有状态计算的函数 例如push 3 >>= \_ -> push 5 >>= \_ -> pop

  • 为什么pop没有参数,而建议函数f需要1个参数?

    pop不带参数,因为它被State包裹 类型为s -> (a, s)的unwapped函数需要一个参数。该 同样适用于push 你可以解开runState

    runState pop :: Stack -> (Int, Stack)
    runState (push 3) :: Stack -> ((), Stack)
    

    如果您的意思是“>>=f的右侧,则f将类似于\a -> pop\a -> push 3,不只是pop


长说明:

这3件事让我更了解State Monad和Stack的例子。

  • 考虑绑定运算符(>>=

    的参数类型

    Monad类型类中绑定运算符的定义是这个

    (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
    

    在堆栈示例中,mState Stack
    如果我们将m替换为State Stack,则定义可以是这样的。

    (>>=) :: State Stack a -> (a -> State Stack b) -> State Stack b 

    因此,绑定运算符的左侧参数类型为State Stack a 右边的那个将是a -> State Stack b

  • 将do notation转换为绑定运算符

    以下是本书中使用do notation的示例代码。

    stackManip :: State Stack Int  
    stackManip = do  
         push 3  
         pop  
         pop  
    

    可以使用bind运算符将其转换为以下代码。

    stackManip :: State Stack Int  
    stackManip = push 3 >>= \_ -> pop >>= \_ -> pop
    

    现在我们可以看到绑定运算符的右侧是什么 他们的类型是a -> State Stack b

    (\_ -> pop) :: a -> State Stack Int
    (\_ -> push 3) :: a -> State Stack ()
    


  • 在实例声明

    中识别(State s)(State h)之间的区别

    这是本书中State的实例声明。

    instance Monad (State s) where  
        return x = State $ \s -> (x,s)  
        (State h) >>= f = State $ \s -> let (a, newState) = h s  
                                            (State g) = f a  
                                        in  g newState 
    

    考虑使用Stack示例的类型,(State s)的类型将是

    (State s) :: State Stack
    s :: Stack
    

    (State h)的类型将是

    (State h) :: State Stack a
    h :: Stack -> (a, Stack)
    

    (State h)是绑定运算符的左侧参数,其类型为State Stack a,如上所述。

    那么为什么h成为Stack -> (a, Stack)
    它是对newtype包装器中定义的State值构造函数进行模式匹配的结果。 (State g)也是如此。

    newtype State s a = State { runState :: s -> (a,s) }
    

    通常,h的类型是s ->(a, s),表示有状态计算。以下任何一项都可能是Stack示例中的h

    runState pop :: Stack -> (Int, Stack)
    runState (push 3) :: Stack -> ((), Stack)
    runState stackManip :: Stack -> (Int, Stack)
    

    就是这样。