在 Haskell 中包含 Random 和 List 的状态 Monad

State Monad containing Random and List in Haskell

在我学习函数式编程考试时,我仍在努力真正理解 Monad。还有什么比自己定义更好的方法呢?我这样定义:

newtype ST a = ST (State -> ([a], State))
type State = StdGen

基本上是一个 List Monad 和一个 Random Monad。 这个 monad 应该使您能够使用随机函数和列表。 现在麻烦来了,因为我能够定义 return 函数,但是 >>= 并不能完全解决问题。

instance Monad ST where
    return a = ST $ \g -> ([a], g)
    -- M a -> (a -> M b) -> M b
    st >>= f  = ST $ \s -> 
                let (x,s') = st s 
                in (f x) s'

代码灵感来自 This paper p.218

有什么解释吗?

让我们小心地跟踪所有类型(我在编写棘手的代码时自己这样做)。首先让我们为您的 ST 类型添加一个访问器,这将使事情变得更容易。

newtype ST a = ST { runST :: State -> ([a], State) }

现在我们有 runST :: ST a -> State -> ([a], State)。在定义 monad 代码时,我喜欢立即将 runST 应用于所有 ST 值,因此我知道我真正使用的是什么类型。

st >>= f = ST $ \s ->
    -- runST st  :: State -> ([a], State)
    -- f         :: a -> ST b
    -- runST . f :: a -> State -> ([b], State)
    -- s         :: State
    let (as, s') = runST st s in  -- I renamed x to as for readability
    -- as        :: [a]
    -- s'        :: State

现在我们需要 ([b], State)。我们可以通过使用f得到bs。我们有一个 a 的列表,所以让我们尝试映射

    -- map (runST . f) as :: [State -> ([b], State)]

嗯,这不是很有用,让我们也尝试应用我们进入的状态:

    -- map (\a -> runST (f a) s) as :: [([b], State)]

也许我们可以解决这个问题。我们有一个 b 列表的列表,以及一些其他的东西。我们将其命名为 rs(对于 "results"):

    let rs = map (\a -> runST (f a) s) as in

现在我们可以通过连接所有结果 bs:

来得到 bs 的列表
    let bs = concat (map fst rs) in  -- bs :: [b]

所以这大概就是我们想要的 return。现在我们要选择哪个State?我们遇到了一个问题,因为我们有很多不同的 State 可供选择。我们是选择列表中的最后一个,还是第一个?如果列表是空的,也许我们只是 return 进来的 State。这些是任意选择——正如我的一位物理学教授曾经说过的:"now we have to make a choice, which is a problem, because we're about to make the wrong one"。这在函数式编程中非常正确。每当您必须像这样做出任意选择时,您可能就搞砸了。

如果我们退后一步,直观地思考其含义,ST a 计算需要一个状态,return 是一个具有新状态的选项列表,可用于未来的计算,其中每一个 都会产生一个选项列表和一个新状态。我们可以使用 concat 将所有列表组合在一起,但我们没有办法将所有状态组合在一起。对于随机 API 我们没有这个(我们可以想象也许 bitxor 将所有状态放在一起......)。

如果没有办法组合状态,我们的 monad 绑定将不得不忘记它拥有的大部分数据,这是一个很好的迹象,表明它不会遵守法律(尽管我担心 monad 这么复杂证明定律的复杂性)。据我所知,没有这种类型的 monad。

一个monad这个类型:

newtype ST' a = ST' { runST' :: State -> [(a, State)] }

并且相当于StateT State []。现在计算的每个分支都有自己的随机状态,所以我们不再需要将许多状态组合成一个。定义这个 monad 可能运气更好——像我做的那样,写下你知道的所有类型,并尝试以类型导向的方式获得你需要的东西。尽量不要 "forget" 任何信息,并力求在构造输出时每个输入只使用一次。

抱歉,这个 post 有点含糊不清 -- 我意识到我在定义 monad 时使用了很多直观的原则,我想我会尝试分享它们。请记住,仅仅获得类型检查的定义是不够的(尽管这确实让你在那里有很多方法):也要检查法律,否则当你使用 do 符号时你会发生奇怪的事情并且之类的

之后,我们得到

st >>= f = ST (g . runST st)
    -- runST st  :: State -> ([a], State)
    -- f         :: a -> ST b
    -- runST . f :: a -> State -> ([b], State)

    where
        g (a:as,s) = let  (bs, s2) = (runST . f) a s 
                          (rs, sn) = g (as, s2)
                     in   (bs ++ rs, sn) 
        g ([], s)  = ([], s) 

(未测试)。