5

我以为我对 Haskell Monads 有很好的处理,直到我意识到这段非常简单的代码对我来说毫无意义(这是来自关于 State monad 的 haskell wiki):

playGame :: String -> State GameState GameValue
playGame []     = do
  (_, score) <- get
  return score

让我困惑的是,当提供的唯一参数是字符串时,为什么允许代码调用“get”?似乎它几乎是凭空产生了价值。

我提出这个问题的一种更好的方法可能是,如何使用>>=and lambda's 而不是 do 表示法重写这个函数?我自己无法弄清楚。

4

2 回答 2

7

将其脱糖成do符号看起来像

 playGame [] =
   get >>= \ (_, score) ->
   return score

我们也可以用fmap

 playGame [] = fmap (\(_, score) -> score) get
 playGame [] = fmap snd get

现在的诀窍是意识到这get是一个与其他类型一样的值

 State s s

直到我们get将计算提供给runState或类似的地方,我们为我们的状态提供一个明确的起始值,才能确定返回的内容。

如果我们进一步简化它并摆脱我们将拥有的状态单子

playGame :: String -> (GameState -> (GameState, GameValue))
playGame [] = \gamestate -> (gamestate, snd gamestate)

state monad 只是包装了所有这些手动传递,GameState但您可以将其get视为访问我们的“函数”传递的值。

于 2014-04-03T15:20:27.383 回答
0

monad 是一个“事物”,它接受一个上下文(我们称之为 m)并“产生”一个值,同时仍然尊重 monad 法则。我们可以将其视为单子的“内部”和“外部”。单子定律告诉我们如何处理“往返”——出去然后回到里面。特别是,定律告诉我们 m (ma) 本质上与 (ma) 是同一类型。

关键是 monad 是这种往返事物的概括。join squashes (m (ma))'s into (ma)'s, and (>>=) 从 monad 中取出一个值并将一个函数应用到 monad 中。换句话说,它将一个函数 (f :: a -> mb) 应用到 (ma) 中的 a —— 这会产生一个 (m (mb)),然后通过 join 将其压缩以获得我们的 (mb)。

那么这与“获取”和对象有什么关系呢?

好吧,do 符号设置我们,以便计算结果在我们的 monad 中。并且 (<-) 让我们从 monad 中提取一个值,以便我们可以将它绑定到一个函数,同时名义上仍然在 monad 内部。因此,例如:

doStuff = do
   a <- get
   b <- get
   return $ (a + b)

注意 a 和 b 是纯的。它们在 get 的“外部”,因为我们实际上窥视了它的内部。但是现在我们在 monad 之外有了一个值,我们需要对它做一些事情 (+),然后将它放回 monad 中。

这只是一点暗示性的符号,但如果我们可以这样做可能会很好:

doStuff = do
  a       <- get
  b       <- get
  (a + b) -> (\x -> return x) 

真正强调它的来回。当你完成一个单子动作时,你必须在该表的右列,因为当动作完成时,'join'将被调用以展平图层。(至少在概念上)

哦,对了,对象。好吧,很明显,OO 语言基本上是在某种 IO monad 中生存和呼吸的。但我们实际上可以进一步分解它。当您按照以下方式运行时:

 x = foo.bar.baz.bin()

你基本上是在运行一个 monad 转换器堆栈,它接受一个 IO 上下文,它产生一个 foo 上下文,它产生一个 bar 上下文,它产生一个 baz 上下文,它产生一个 bin 上下文。然后运行时系统根据需要多次“调用”加入这个东西。请注意这个想法与“调用堆栈”的结合程度。事实上,这就是为什么它在 haskell 方面被称为“monad 转换器堆栈”。它是一堆单子上下文。

于 2014-04-10T06:15:23.870 回答