【问题标题】:State Monad containing Random and List in Haskell在 Haskell 中包含随机和列表的状态单子
【发布时间】:2016-11-08 21:03:35
【问题描述】:

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

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

有什么解释吗?

【问题讨论】:

  • 那么你希望 monad 做什么?我不认为你可以把这种类型变成一个单子。
  • 真的,方法是倒退的。你应该从你想要的行为开始,然后写下你需要实现它的类型。我的短语“将这种类型变成 monad”是一个糟糕的短语。
  • 我想要的行为是将这样的函数链接在一起(从语义上看):exmpl :: Int -> StdGen -> ([Int], StdGen) exmpl x gen = [y | y <- [x - 3 .. x + randomR (1, 10) gen]
  • f :: a -> ST a b,但x :: [a]。您不能将f 应用于x,因此您要么必须选择一个元素(哪个?),要么将其应用于部分或全部元素并弄清楚如何处理所有生成的ST a b 值.
  • 但是“将 [...] 这样的函数链接在一起”意味着是什么?如果你明白你的意思,你写(>>=)就没有问题了。

标签: haskell monads state-monad


【解决方案1】:

让我们小心地跟踪所有类型(当我编写棘手的代码时,我自己会这样做)。首先让我们为您的 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。我们有as 的列表,让我们尝试映射

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

嗯,这没什么用,让我们也尝试应用我们进入的状态:

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

也许我们可以解决这个问题。我们有一个b 列表列表和其他一些东西。我们将其命名为rs(代表“结果”):

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

现在我们可以通过连接所有结果 bs 得到bs 的列表:

    let bs = concat (map fst rs) in  -- bs :: [b]

所以这大概就是我们想要返回的内容。现在我们要使用哪个State?我们有一个问题,因为我们有很多不同的States 可供选择。我们选择列表中的最后一个还是第一个?如果列表为空,也许我们只返回进来的State。这些是任意选择——正如我的一位物理学教授曾经说过的:“现在我们必须做出选择,这是一个问题,因为我们是即将犯错”。这在函数式编程中非常正确。每当您必须做出这样的任意选择时,您都可能搞砸了。

如果我们退后一步,直观地思考其含义,ST a 计算会获取一个状态并返回一个带有新状态的选项列表以用于未来的计算,每个选项将产生一个选择列表和一个新状态。我们可以使用concat 将所有列表组合在一起,但我们没有办法将所有状态组合在一起。使用随机 API,我们没有这个(我们可以想象可能 bitxoring 将所有状态...)。

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

一个带有 this 类型的 monad:

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

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

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

【讨论】:

  • newtype ST' a = ST' { runST' :: State -> [(a, State)] }(RHS 上没有括号a)?
  • g (f:fs) s = let {(bs,s2) = f s ; (rs,sn) = g fs s2} in (bs++rs,sn) ; g [] s = ([],s)处理fs :: [State -> ([b], State)]不是很自然吗?
  • @WillNess,啊,就像“concatM”,很有趣。我想知道这是否会导致 ST 成为一个单子。
  • 更像msum 应用于map f as,如果我们定义ST f <|> ST g = ST $ \s -> let (a, r) = f s; (b, q) = g r in (a ++ b, q)
  • @WillNess,又名(<|>) = liftM2 (++) in State(模新类型)
【解决方案2】:

按照luqui's lead,我们得到

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) 

(未测试)。

【讨论】:

    猜你喜欢
    • 2010-12-29
    • 1970-01-01
    • 2019-12-01
    • 2015-08-24
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    • 2021-03-06
    • 1970-01-01
    相关资源
    最近更新 更多