【问题标题】:Why can I call a monadic function without supplying a monad?为什么我可以在不提供 monad 的情况下调用 monadic 函数?
【发布时间】:2014-04-03 15:15:00
【问题描述】:

我以为我对 Haskell Monads 掌握得很好,直到我意识到这段非常简单的代码对我来说毫无意义(来自haskell wiki about the State monad):

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

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

我提出这个问题的一种更好的方法可能是,如何使用&gt;&gt;= 和 lambda's 而不是 do 表示法重写这个函数?我自己也想不通。

【问题讨论】:

  • 代码不是“调用”获取。 “get”不是一个函数,它是一个多态值:hackage.haskell.org/packages/archive/transformers/latest/doc/…
  • 谢谢,这对我来说实际上是一个主要的困惑点。对于来自 OO 世界的人来说,这几乎是一个不幸的名字,其中类通常具有“getThis”和“getThat”方法。我只是假设既然“get”是一个动词,它一定是一个函数。
  • 即使在 OO 领域,也没有那么简单。特别是因为 OO getter 不是函数——它们实际上更接近于多态值,就像 Haskell 的 get 一样。不同之处在于 OO 语言中的 monad 将值附加到对象,而我们将值附加到具有任意语义的任意 monad。我会回答解释我的意思。

标签: haskell monads state-monad


【解决方案1】:

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

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

我们也可以用fmap写这个

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

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

 State s s

直到我们将计算提供给runState 或类似我们为状态提供显式起始值的类似位置,才能确定get 将返回的内容。

如果我们进一步简化它并摆脱我们将拥有的 state monad

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

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

【讨论】:

  • 所以从某种意义上说,State monad 隐藏了状态的传递(正如您所提到的)。我可以欣赏这里的“魔力”。但在 runState 的核心中,“get”的值肯定是“保持”的……对吧?
  • @parker.sikand 所以State 只是文字函数s -&gt; (s, a) 之上的一个包装器。 runState 是我们用来实际应用这个函数的。您可以将其视为一种“加汤”$。所以我想runState 持有get 的值与(\x -&gt; ...) $ a $ 持有x 的值一样多
【解决方案2】:

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

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

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

好吧,do 符号设置我们,以便计算结果在我们的 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) 

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

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

 x = foo.bar.baz.bin()

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-14
    • 2012-03-02
    • 2015-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 1970-01-01
    相关资源
    最近更新 更多