【问题标题】:State Monad never ends状态单子永远不会结束
【发布时间】:2013-08-12 10:33:52
【问题描述】:

有人能告诉我为什么这段使用 State monad 的代码永远不会结束吗?

fib' :: State [Int] ()
fib' = do l <- get
          let l2 = sum (last2 l)
          put (l ++ [l2])
          return ()
          fib'

take 10 $ execState fib' [0, 1]

当我在 ghci REPL 中执行它时,该函数执行时不会停止。

【问题讨论】:

    标签: haskell state


    【解决方案1】:

    execState 的结果是计算结束时的状态值。但是你的计算永远不会结束:fib' 调用fib'。虽然在您的情况下可以证明状态的前 10 个元素最终不会改变,但编译器无法预测(也不应该)。

    您可能想查看 Writer Monad,您可以在其中懒惰地处理写入的数据。

    【讨论】:

    • 我正在使用 Control.Monad.State.Lazy。即使 fib' 无休止地进行下去,它不会只执行必要的操作吗?如果我简单地将 execState fib' [0,1] 放在一行上,那么我会理解计算可能会永远持续下去。但是考虑到 Haskell 是懒惰的,不应该只返回 10 $ execState fib' [0, 1] 来满足请求吗?
    • 懒惰不能变魔术。即使fib 已经递归了几次,也无法知道稍后对fib 的调用可能不会将更改声明为完全不同的内容。请注意,您总是设置whole状态;事实上,你碰巧使用l ++ something 这只是你的特定程序的一个特点。
    • @Jay,看看我的回答,它告诉你如何知道它不会在不运行它的情况下终止。
    【解决方案2】:
    1. return () 行是多余的,可以删除
    2. (++) 几乎总是一个坏主意,因为它的第一个参数的大小为 O(N)。
    3. 您构建 许多 个长度不断增加的列表,而您的目标是构建一个无限列表

    【讨论】:

      【解决方案3】:

      return 不像在命令式语言中那样工作。对于每个 monad,它的定义不同,对于 state monad,它被定义为: return x = State $ \s -&gt; (x,s)

      所以它实际上并没有脱离fib' 函数,而是将一个新的State monad 绑定到其余的计算。

      编辑:

      虽然我最初的答案是正确的,但它并没有回答 OP 的问题。在 OP 的代码中,return 充当空操作。对于 OP 的代码永远不会终止的原因,请参阅其他答案。我仍然在这里留下我的答案,因为我觉得指出这一点仍然很重要,因为return 确实不必要地出现在 OP 的代码示例中

      【讨论】:

        【解决方案4】:

        您在这里所做的或多或少等同于以下内容:

        rfib (last:last':rest) = let new = last + last' in rfib (new:last:last':rest)
        

        它试图“懒惰地”以相反的顺序创建数字。 (顺序无关紧要,我只是反过来做了,以摆脱分散注意力的 (++) 和 last2)。

        但重点是,您从不计算列表,因为类型检查器会告诉您。 例如,以下都是很好的类型:

        res1 = (take 10 . rfib) [1,0] :: [Int]   
        res2 = (take 10 . snd . rfib) [1,0] :: [Int]   
        res3 = (take 10 . snd . snd . rfib) [1,0] :: [Int]   
        res4 = "foo" == rfib [1,0]   
        

        你知道,有时不写注释是一个好主意。 然后你会看到类似这样的推断:

        rfib :: Num a => [a] -> b
        

        这与您的 foo' 的主要类型密切对应:

        fib' :: Num a => State [a] b
        

        这种类型的你应该考虑一下。在rfib 的情况下,它告诉您不知从何处创建了您想要的任何类型的值,这里称为b。 这就是“没有这样的价值”的同义词。就像在 head []unJust Nothingerror "Bottom" 中一样,任何计算该值的尝试都必须发散。

        当结果中出现的新类型变量被类型构造函数“保护”时,情况就不同了。然后会发生什么将取决于类型,其构造函数被应用。就目前而言,它适用于 Writer,但不适用于 State。不过,这种出乎意料的类型应该让人三思而后行。

        【讨论】:

        • 不完全正确。如果您采用他的递归、永无止境的代码,但将其放入 writer monad 以获取fib [0,1] :: MonadWriter [Int] m =&gt; m b 类型的内容,那么take 10 $ execWriter (fib [0,1]) 就可以正常工作,尽管返回类型中有b。所以他的整体尝试并没有你说的那么糟糕,这只是一个错误的单子。
        • 这完全不同,@JoachimBreitner,因为 m 类型出现在 => 箭头的左侧,因此不会凭空出现。很明显,受构造函数保护的新类型变量并不表示未定义,如Nothing :: Maybe uMonad m =&gt; m u 一样。
        • 在某一时刻,您说永远不会计算列表。让我们假设我是写rfib 的人。让我们还假设我在跳入 ghci 提示符测试 rfib 之前最后看一下 rfib 的正文以确认我写的内容是正确的。现在,你能告诉我,哪条信息会让我意识到 rfib 的计算永远不会收敛?
        • @Ingo, m 不是相关的类型变量; b 是。我应该写fib [0,1] :: Writer [Int] b;现在与fib' :: Num a =&gt; State [a] b 类型的类比已经完成。
        • @Jay 事实上它的类型就是我上面所说的,这使得res1res4 的所有四个示例的类型都很好。后者看起来不奇怪吗?
        【解决方案5】:

        根据@Joachim Breitner 的建议,使用惰性 Writer monad 的可能解决方案是:

        import Control.Monad.Writer
        
        fib' :: Writer [Int] ()
        fib' = tell [1, 1] >> go 1 1
            where
                go :: Int -> Int -> Writer [Int] ()
                go f1 f2 = do
                    let f3 = f1 + f2
                    tell [f3]
                    go f2 f3
        
        main = print $ take 10 $ execWriter fib'
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-17
          • 2013-04-09
          • 2010-12-27
          • 1970-01-01
          • 2021-08-05
          • 2011-01-20
          • 2017-11-05
          • 2019-05-21
          相关资源
          最近更新 更多