【问题标题】:Is there a sensible way to unzip the state monad?有没有一种明智的方法来解压缩状态单子?
【发布时间】:2026-01-16 01:00:01
【问题描述】:

我想要一个类似的功能:

unzipState :: (MonadState s m) => m (a, b) -> (m a, m b)

这将采用返回元组的(有状态的)计算,并将返回两个(依赖的)计算。

当然,困难在于从一个或另一个计算中提取值应该更新另一个计算中的状态。

一个有用(和激励)的应用程序是 Random monad,表示为

{-# LANGUAGE Rank2types #-}
import qualified System.Random as SR
import Control.Monad.State

type Random a = forall r. (State RandomGen r) => State r a

假设你有:

normal :: Random Double
-- implementation skipped

correlateWith :: Double -> Random (Double, Double) -> Random (Double, Double)
correlateWith rho w = do
                        (u, v) <- w
                        return $ (u, p * u + (1 - p * p) * v)

会写是很自然的:

let x = normal
    y = normal
    (u, v) = unzipState $ correlateWith 0.5 $ liftM2 (,) x y
    ... now I am able to perform computation on u and v as correlated random variables

有没有明智的方法来做到这一点?我挣扎了一下,但没有设法得到任何东西。 Hoogle 也无济于事。

编辑

很好的答案表明我的问题定义不明确。尽管如此,有人可以解释我为什么以下python中的实现(我认为是正确的,但没有进行太多测试)不能在Haskell中翻译(具有STrefs,闭包和其他东西的魔力我承认我不明白;-)):

def unzipState(p):
    flist, glist = [], []
    def f(state):
        if not flist:
            (fvalue, gvalue), newstate = p(state)
            glist.insert(0, gvalue)
            return (fvalue, newstate)
        else:
            fvalue = flist.pop()
            return (fvalue, state)
    def g(state):
        if not glist:
            (fvalue, gvalue), newstate = p(state)
            flist.insert(0, fvalue)
            return (fvalue, newstate)
        else:
            gvalue = glist.pop()
            return (gvalue, state)
    return (f, g)

我并不是说可以在 Haskell 中翻译有状态代码,但我觉得理解 whywhen(即使是在示例中)它无法完成会大大提高了我的理解力。

edit2

现在很清楚了。函数 f 和 g 显然不是纯函数,因为它们的输出不仅取决于 state 的值。

再次感谢!

【问题讨论】:

  • 既然你提到了随机值生成器的例子:*.com/a/32000364;它的界面类似于您正在寻找的界面。

标签: haskell random state monads


【解决方案1】:

不可能构造一个通用函数unzipState 来做你想做的事,如果只是因为你可能无法为其预期效果提供正式的规范。换句话说,假设你已经实现了某个函数unzipState。你怎么知道它是正确的?您必须证明它满足某些定律/方程,但这里的麻烦是首先要找到这些定律。


虽然我想我理解你想要做什么,但 Random monad 也清楚地表明了为什么它不能完成。要看到这一点,您必须放弃具体实现 type Random a = ... 并考虑由

给出的抽象解释

v :: Random a 表示va 类型值的概率分布

“绑定”操作(&gt;&gt;=) :: Random a -&gt; (a -&gt; Random b) -&gt; Random b 只是一种从旧概率分布构造新概率分布的方法。

现在,这意味着unzipState 只返回一对概率分布,可以用来构造其他概率分布。关键是虽然do 语法看起来很有启发性,但您实际上并没有对随机变量进行抽样,您只是计算概率分布。随机变量可以相关,但概率分布不能。


请注意,可以创建对应于随机变量的不同 monad RandomVariable a。但是,您必须提前固定样本空间 Ω。实现是

type RandomVariable a = Ω -> a



如果你想要随机变量和自动扩大样本空间的能力,你可能需要两个绑定操作

bind1 :: Random Ω a -> (a -> Random Ω b) -> Random Ω b
bind2 :: Random Ω1 a -> (a -> Random Ω2 b) -> Random (Ω1,Ω2) b

还有一些依赖类型的魔法来处理像(Ω1,(Ω2,Ω3))这样的产品泛滥。

【讨论】:

  • 我认为 (Random a) 不能真正解释为概率分布,因为在这种情况下,您需要能够将 omega 定义为概率化空间,这非常困难。此外,并不是因为 Random a 的两个对象具有相同的分布,所以它们相等(取两个 u = uniform;v=uniform,你没有 u=v)。一个更谦虚的解释是将其理解为随机变量的实现(更具体地说,作为对实现进行操作的函数),它应该与流同构,所以我相信解压缩操作是明确定义的)。
  • 不。如果u = uniform :: Random Av = uniform :: Random A,那么u = v = uniform 是微不足道的。请记住,这里有两个不同的概念:从样本空间 Ω 派生的随机变量,以及以概率(= 概率分布)加权的 a 类型值的集合。您的 Random a monad 对应于后者,而不是前者。同样可以将其实现为Random a = [(a,Probability)]。 [恐怕在没有正式化 Random a monad 和你所追求的特定效果的情况下讨论这个有点棘手。]
  • 你在平等点上是完全正确的,我很困惑。谢谢!
【解决方案2】:

我不完全清楚你希望它如何工作——因为correlateWith 对一个元组进行操作,相关变量独立有什么意义?假设你这样做:

let x = normal
    y = normal
    (u, v) = unzipState $ correlateWith 0.5 $ liftM2 (,) x y
in do u1 <- u
      v1 <- v
      u2 <- u
      u3 <- u
      -- etc...

u1u2u3之间应该存在什么关系?

此外,像这样的“随机变量”可以直接转换为无限惰性流。以下是什么意思?

let x = normal
    y = normal
    (u, v) = unzipState $ correlateWith 0.5 $ liftM2 (,) x y
in do vs <- generateStream v
      u1 <- u
      if someCondition u1 then u else return u1
      -- etc...

因为从u 采样的值的数量会根据u1 而变化,这似乎表明绑定到vs 的非单子值也会以某种方式追溯依赖于u1,这听起来很可疑Haskell 避免的那种远距离的诡异动作。

我会大胆猜测,您要完成的工作不能简单地在简单的 PRNG 状态单子之上进行改造,但可能还有其他选择。

【讨论】:

  • 我猜语义将与 Streams 完全相同:data Stream a = a : (a:
  • 在之前的评论中,请阅读 (u1, v1) are associated 而不是 (u1, u2) are associated。
【解决方案3】:

Haskell 中有很多关于贝叶斯单子的相关内容。这是一个参考:http://www.randomhacks.net/articles/2007/02/22/bayes-rule-and-drug-tests

另请参阅此页面提供的“纯功能惰性非确定性编程”:http://www.cs.rutgers.edu/~ccshan/

编辑:我也看不出为什么你不能只给correlateWith以下类型签名,并直接在do块中运行它,而不是尝试在let绑定中“推送”随机状态。 correlateWith :: Double -&gt; Random Double -&gt; Random Double -&gt; Random (Double, Double)

【讨论】:

  • 这是很有趣的材料,但你能指出它与我的问题有什么关系吗?
  • 它展示了如何做实际的概率单子,而不是仅仅重载随机和失败。