【问题标题】:Understanding bind function in Haskell了解 Haskell 中的绑定函数
【发布时间】:2012-02-22 20:40:56
【问题描述】:

我熟悉范畴论中的 monad(实际上它们是一个非常简单的概念),但 Haskell 中的 >>= 函数完全让我感到困惑。好的,所以将绑定应用于M a 和函数a -> M u 与首先将monad 应用于此函数,然后在指定值处对其进行评估并乘以结果:a >>= f 与@ 相同987654326@。但这是对计算的自然描述吗?是否有一些有用的方法可以帮助我理解它?

是否有一些不错的文章不是面向刚从 C++ 丛林中出来的人?

【问题讨论】:

  • 我认为>>= 更多地代表了 monad 的“管道”方面,这实际上很重要,但理论上很无趣。我发现为 monad 实现 join 通常比 >= 更容易,我想使用 join 来定义 monad 会更聪明。
  • @Landei 您始终可以将其定义为a >>= f = join (fmap f a) where join = ...;,假设已经存在Functor 实例。
  • 是的,我知道,但是最好已经定义了这两种方法(就彼此而言),并将选择哪一种方法留给用户(例如 @987654333 @ 和 Eq.(/=))。

标签: haskell monads


【解决方案1】:

考虑一元函数组合运算符<=<。这类似于.,除了它适用于一元函数。它可以简单地用>>= 来定义,所以了解一个可以让我们了解另一个。

(<=<) :: (a -> m b) -> (b -> m c) -> a -> m c
(f <=< g) x =  g x >>= f

(.) :: (a -> b) -> (b -> c) -> a -> c
(f . g) x = g x |> f
  where z |> h = h z

.的情况下,先“执行”g,然后在g的输出上执行f。对于&lt;=&lt;,首先“执行”g及其效果,然后执行f及其效果。实际上,说一个发生在另一个“之前”有点用词不当,因为并非所有 monad 都以这种方式工作。

也许更准确的说法是f 可以利用g 提供的额外上下文信息。但并不完全正确,因为g 可能带走上下文信息。如果你想 100% 正确地描述 monad,你真的必须在蛋壳上行走。

但在几乎所有重要的情况下,f &lt;=&lt; g 意味着单子函数g效果(以及结果)随后会影响行为一元函数f.


解决有关v &gt;&gt;= f = join (fmap f v)的问题

考虑f :: a -&gt; m bv :: m afmap f v 是什么意思?好吧fmap :: (c -&gt; d) -&gt; m c -&gt; m d,在这种情况下c = ad = m b,所以fmap f :: m a -&gt; m (m b)。现在,当然,我们可以将v :: m a 应用到这个函数上,得到m (m b)。但是结果类型m (m b) 是什么意思确切

inner m 表示来自f 的上下文。 外部 m 表示源自v 的上下文(注意fmap 不应干扰此原始上下文)。

然后你 join 那个 m (m b),将这两个上下文一起粉碎成 m a。这是Monad 定义的核心:您必须提供一种将上下文粉碎在一起的方法。您可以检查各种Monad 实例的实现细节,以尝试了解它们如何“粉碎”上下文。但是,这里的要点是,“内部上下文”在您将其与“外部上下文”合并之前是不可观察的。如果你使用v &gt;&gt;= f,那么就没有函数f 接收纯值a 并产生简单的一元结果m b实际 概念。相反,我们知道fv 的原始上下文中作用

【讨论】:

  • 如果f 的域不是一元的,它如何利用额外的上下文信息?
  • @AlexeiAverchenko 因为它的范围 monadic。这正是 monad 的魔力:即使 monadic 函数的输入 不是 monadic,输出 is。因此,&gt;&gt;= 是一种传达一元值 m a 的上下文的方法,以影响一元函数 a -&gt; m b 的结果。请注意,m 对于这两者必须相同。这样做是没有意义的,比如[1,2,3] &gt;&gt;= print,因为 [] 和 IO 不一样。
  • 但是函数a -&gt; m b无法访问m a的附加数据,这些数据只是简单地乘以join :: m m b -&gt; m b
  • @AlexeiAverchenko 我已经扩展了我对此事的更多想法的答案。
  • @AlexeiAverchenko 我不是范畴理论家,但我听说它被解释为“Monads 用于将上下文粉碎在一起,Comonads 用于将上下文分开”。如果您正在寻找使用 Monads 建模 IO 的理由,您可能对Philip Wadler's monad papers 感兴趣,包括但不限于如何声明命令式命令式函数式编程。但是Monad(似乎几乎是偶然的)被证明是一个有用的抽象,而不仅仅是 IO。
【解决方案2】:

嗯。我认为一个很好的思考方式是&gt;&gt;= 让您compose 计算;计算本身的格式为a -&gt; m b。所以m b 只是表示计算的结果

所以计算只需要一些值并产生一些结果。一个很好的例子是列表类型:a -&gt; [b] 表示非确定性计算。它需要一个输入,但可以产生多个结果。就其本身而言,a -&gt; [b] 作为计算是有意义的。但是你会如何结合这些呢?很自然的答案是,您将对所有之前的结果执行每个连续的“计算”。这正是&gt;&gt;= 对列表所做的。

真正帮助我看到其实际价值的一件事是考虑 DFA 和 NFA。您可以想象在 Haskell 中简单地编写一个 DFA,如下所示:

data State = S1 | S2 | S3 | S4 | Q
data Input = A | B
transition :: State -> Input -> State
transition S1 A = S2
transition S1 B = S3
-- and so on...

然后我们可以折叠输入:

 foldl transition S1 [A, A, B, B]

现在,我们如何将这段代码推广到 NFA?嗯,NFA 的转换“函数”可以被认为是一种非确定性计算。所以我们定义如下:

transition S1 A = [S1, S2]
transition S1 B = []

但现在我们必须做一些奇怪的体操才能使用foldl!令人高兴的是,我们可以改为 foldM。所以这里由 monad 建模的“计算”是非确定性转换函数。

【讨论】:

    【解决方案3】:

    也许=&lt;&lt; 从计算的角度更容易理解(它只是flip (&gt;&gt;=))。它有输入(=&lt;&lt;) :: (Monad m) =&gt; (a -&gt; m b) -&gt; m a -&gt; m b,并且对应于函数应用的类型,参见($) :: (a -&gt; b) -&gt; a -&gt; b。所以&gt;&gt;= 只是一元级别的翻转函数应用程序。

    另外,(&gt;&gt;=) 用于去糖 do 符号,do 在语法上与命令式代码非常对应(在合适的单子中)。

    【讨论】:

    • 或者你可以把=&lt;&lt;想象成一个提升函数。它提升了一个单子函数a -&gt; m b 也接受单子输入m a -&gt; m b
    【解决方案4】:

    下面是它作为计算模型如何工作的粗略概念:类型构造函数MMonad 的实例代表一个参数数据结构,该结构的非参数部分可以包含其他信息. returnjoin 对应于结构中那些部分的某种幺半群。函数a -&gt; M b 基于a 类型的输入在该结构中引入信息。因此,通过将函数a -&gt; M b 提升到M a -&gt; M b,我们使用M 的参数信息来创建一些非参数信息,然后将其与类型为M a 的值中已经存在的信息相结合。

    a -&gt; M b 类型的不对称性质为非参数信息的流动提供了固有的方向,而关联性要求意味着整体顺序是唯一重要的事情。

    最终结果是使用某种具有内置因果概念的上下文来扩充函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多