【问题标题】:Haskell: iterate in State, how to force the behaviour I want?Haskell:在State中迭代,如何强制我想要的行为?
【发布时间】:2011-10-28 10:21:27
【问题描述】:

这是我在 SO 上的第一篇文章,我对 Haskell 比较陌生,所以请原谅任何失误或我的代码不习惯!

考虑以下两个直观的描述:a, f(a), f(f(a))...

答. 一个列表,包含:a、f 对 a 的应用、f 对 that 的应用、f 对 that...

的应用

乙。 一个列表,在第 i 个位置包含 f 到 a 的 i 嵌套应用程序。

我的问题是我在尝试使用 Haskell 中的 iterate 函数来执行 A 时被烧毁了。我的实际应用程序是一个模拟,但下面的人为示例突出了这个问题。

import Control.Monad.State

example :: State Int [[String]]

step :: [String] -> State Int [String]
step l = do
         currentState <- get
         let result = if (currentState == 1)
                          then "foo":l
                          else "bar":l
         put (currentState + 1)
         return result

example = do
          sequence $ take 3 . iterate (>>= step) $ return []

有了这些定义,

evalState example 1

结果:

[[],["foo"],["bar","bar"]]

显然,iterate 是 B,而不是 A!因为step 函数只添加 到输入列表中,所以step ["foo"] 不可能导致["bar", "bar"],无论是什么状态!

让我说我确实了解这里发生了什么,而且 - 正式 - 结果正是“应该是”:step 是一个有状态的函数,所以当f(a) 作为 f(f(a)) 的一部分进行评估,它将被重新计算,而不是从第二个列表项中取出,因为状态已经改变。我也意识到我可以通过将累积列表放入状态中来避免在我的实际应用程序中发生这种情况。

不过,发布此内容有两个原因。

首先,重点是iterate 经常被解释为可能误导初学者认为它是A,而实际上它是B。这包括Learn You A Haskell(否则我发现它非常有用),但也发布在 SO(例如here 和here)。事实上,LYAHFGG中iterate的口头解释几乎就是上面的定义A。因此,在此发布一个帖子可能是有用的,作为其他 Haskell 新手的资源,他们因此而遇到错误并正在寻找解释(因此,一定要发布更准确、技术性、措辞更好的澄清A 和 B 之间的区别)。

其次,我仍然对是否有一个实际上会执行A的函数感兴趣!换句话说,在上面的有状态示例中,我如何生成列表(稍微滥用符号):[a, b = f(a), f(b), ...]?换句话说,给定

example2 = do
           firstResult <- step []
           secondResult <- step firstResult
           return $ [[], firstResult, secondResult]

为了什么

evalState example2 1

产生预期的结果

[[],["foo"],["bar","foo"]]

如何使用iterate 重写example2?

在初学者 Haskell 列表中,发布了一个关于 iterate 的记忆版本的 related question。但是,该查询似乎没有收到答案。

我不完全确定懒惰真的是我的应用程序中的问题。 iterate 的严格版本会做我想要的吗?如下所示,我自己的、幼稚的“严格迭代”似乎没有任何区别。

iterate' f x = x : rest
               where previous = f x
                     rest = previous `seq` iterate f previous

我们将不胜感激任何有关这一切的见解!

【问题讨论】:

    标签: haskell iteration loops state-monad


    【解决方案1】:

    A. 和 B. 之间没有区别,它们是同一个东西,通过引用透明性。
    问题的核心似乎是您在执行有状态计算的上下文中解释它们。在这种情况下,您期望的 A 的类似物是
    A':产生一个结果列表: 1. 将初始计算的结果放入列表中, 2. 根据前一个结果确定下一个计算,执行并将其结果附加到列表中, 3. 转到 2.
    B的类似物是
    B':生成一个计算列表(通过迭代(>>= step)),然后通过一个接一个地执行计算,生成一个结果列表。
    对于无状态计算,或者当您将相同的初始状态传递给 B' 中生成的所有计算时,唯一的区别在于效率,但如果您使用 sequence,则每个计算都以不同的状态开始,因此您会得到不同的结果从A'。 分解你的example,我们有

    actionList = take 3 $ iterate (>>= step) (return [])
               = [return [], return [] >>= step, return [] >>= step >>= step]
    

    State Int [String] 中的操作列表(或一元值)。现在,当您向其应用 sequence 时,

    example = sequence actionList
    

    你得到一个动作,当执行时,第一个动作的初始状态,第二个动作的状态由第一个更新,第三个动作的状态由第二个更新。要获得您期望的行为,它们都必须以相同的初始状态运行。

    基本上,State s v 类型的值是s -&gt; (v, s) 类型的函数。 iterate 创建一个函数列表,sequence 应用这些函数,为它们提供不同的s 参数(每个都得到前一个生成的s)。

    为了得到想要的行为,我们必须引入一个新的组合子。非常好,但只能在少数 Monad 中使用

    iterateM :: Monad m => (a -> m a) -> m a -> m [a]
    iterateM step start = do
        first <- start
        rest <- iterateM step (step first)
        return (first:rest)
    

    哪个产生

    Prelude Control.Monad Control.Monad.State> evalState (fmap (take 4) (iterateM step (return []))) 1
    [[],["foo"],["bar","foo"],["bar","bar","foo"]]
    

    但它只适用于足够懒惰的 monads (&gt;&gt;=),Control.Monad.State.Lazy 是少数之一,Control.Monad.State.Strict 是不。即使使用C.M.S.Lazy,你也不能使用iterateM之后的状态,你必须put一个新的状态才能继续计算。为了得到其他 monad 可用的东西,我们可以添加一个 count 参数,

    iterCountM :: (Monad m) => Int -> (a -> m a) -> m a -> m [a]
    iterCountM 0 _ _ = return []
    iterCountM k step start = do
        first <- start
        rest <- iterCountM (k-1) step (step fisrt)
        return (first:rest)
    

    所以我们失去了灵活性,但在更多的单子中获得了可用性。

    【讨论】:

    • 非常感谢您的回复!你在“分解你的例子......”之后所说的一切都非常有道理,这正是我所说的“让我说我确实理解这里发生了什么......”所以感谢您确认这一点。但我仍然不相信这种解释。我打算让 A 表示第二个元素是 step 对第一个元素的完全评估结果的应用,step ["foo"] 等于 ["bar", "bar"],这是不可能的,无论状态如何。考虑到这种解释,该示例实现了 B 而不是 A,因此它们不能相等。
    • 以相同的初始状态运行所有排序的操作在这里很有意义。然而,在我的实际应用程序中,状态还包括一个随机生成器,并且每个step 中都会发生几个随机事件。因此,即使我确保将相同的随机生成器输入结果列表中每个位置的“第一步”,它们也可能导致不同的结果,对吧?但是第二个条目不能再被解释为第一个条目的演变......
    • 啊,但是忽略 newtype 包装器,iterate 会为您提供一个函数列表(模 newtype 和 currying,(&gt;&gt;= step) :: (([String],Int) -&gt; ([String],Int)) -&gt; (([String],Int) -&gt; ([String],Int)))。每个函数都是将此转换应用于前一个函数的结果(由于引用透明性,是否完全评估并不重要)。您希望将前一个函数的应用结果包含在下一个函数的确定中,这是一种不同的模式,而不是迭代。
    • 是的,这正是我心目中的模式。因此,这意味着参考example2(参见编辑原始问题),答案将是:“您不会使用iterate 重写!” ?那么,什么是同样紧凑/优雅/惯用的方式呢?就像我说的那样,我会对在状态内不累积结果的情况下执行此操作的方法感兴趣。
    • 丹尼尔,如果您将第二条评论(带有上下文)放在单独的答案中,我可以投票赞成...(我认为您的第一个答案肯定是对原因的非常清楚的解释example 做了它所做的事情,只是碰巧我正在寻找关于如何让它做其他事情的指导!)
    【解决方案2】:

    这可能无法回答您提出的问题,但您正在做的事情听起来很像unfoldr。

    step (seed, l) = Just (l', (seed', l'))
      where seed' = succ seed
            l' = (if seed == 1 then "foo" else "bar"):l
    

    ghci> take 3 $ unfoldr step (1, [])
    [["foo"], ["bar", "foo"], ["bar", "bar", "foo"]]
    

    不需要单子。我有点在黑暗中刺伤,因为您没有具体说明您实际上要做什么,但是无论我是否得到 step 正确,外卖信息是 unfoldr 可用于简单的状态线程,也是。

    unfoldr :: (seed -> Maybe (val, seed)) -> seed -> [val]
    

    【讨论】:

    • 谢谢@Dan,不错的tipp,而且我确实不知道展开器(尽管我曾研究过使用mapAccumL)。我肯定会将它添加到我的工具包中,但就我目前的目的而言,每个“步骤”都非常复杂,并且可能还会继续扩展,因此拥有 monad 的通用性可能很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 2013-08-06
    • 1970-01-01
    • 2016-08-11
    • 1970-01-01
    • 2021-04-27
    相关资源
    最近更新 更多