Scala 函子和单子的区别

64

请问有人能解释一下Scala环境下Functor和Monad之间的区别吗?


6
这里有一个相当不错的指南:http://gabrielsw.blogspot.com/2011/08/functors-applicative-functors-and.html - Luigi Plinge
@LuigiPlinge:你应该把那个发表为答案。 - missingfaktor
4个回答

49

Scala本身并不太强调Functor和Monad这些术语。我想使用map是表示Functor方面,使用flatMap是表示Monad方面。

对我来说,研究和探索scalaz迄今为止是了解scala上这些函数概念最好的途径(与haskell上下文相比)。两年前我开始学习scala时,scalaz代码对我来说就像天书一样,然后几个月前我重新开始看,发现它真的是该特定风格函数编程的干净实现。

例如,Monad 实现显示monad是一个指向functor的点,因为它扩展了 trait(以及 trait) 。我邀请您去查看代码。它在源代码中有链接,非常容易跟踪。

因此,Functor更普遍。Monads提供附加功能。要了解当您拥有functor或monad时可以做什么,请查看MA

您将看到需要隐式functor(特别是applicative functors)的实用程序方法,例如sequence和有时需要完整monad的方法,例如replicateM。


28

scalaz为参考点,一个类型F[_](即,一个由某个单一类型参数化的类型F)如果可以提升函数,则它是一个函子。这是什么意思:

class Function1W[A, B](self: A => B) { 
  def lift[F[_]: Functor]: F[A] => F[B]
}

也就是说,假设我有一个函数A => B,一个函子F[_],那么现在我就有了一个函数F[A] => F[B]。实际上这只是从相反的角度来看待scala的map方法,忽略了CanBuildFrom的部分,它基本上是这样的:

F[A] => (A => B) => F[B]

如果我有一个字符串列表,以及从字符串到整数的函数,那么我显然可以生成一个整数列表。这适用于Option、Stream等所有函子。

我觉得有趣的是,你可能会马上跳进(错误的)结论,认为函子是包含 A 的“容器”。这是一种不必要的限制。例如,考虑一个函数 X => A。 如果我有一个函数 X => A 和一个函数 A => B,那么显然,通过组合,我就有了一个函数 X => B。但现在,请这样看待它:

type F[Y] = X => Y //F is fixed in X

(X => A) andThen (A => B) is   X => B

  F[A]            A => B       F[B]

因此,对于某个固定的X,类型X => A也是一个函子。在scalaz中,函子被设计为以下特征(trait):

trait Functor[F[_]] { def fmap[A, B](fa: F[A], f: A => B): F[B] }
因此,上面的Function1.lift方法被实现。
def lift[F[_]: Functor]: F[A] => F[B] 
  = (f: F[A]) => implicitly[Functor[F]].fmap(f, self)

一些函数对象实例:

implicit val OptionFunctor = new Functor[Option] {
  def fmap[A, B](fa: Option[A], f: A => B) = fa map f
}

implicit def Functor1Functor[X] = new Functor[({type l[a]=X => a})#l] {
  def fmap[A, B](fa: X => B, f: A => B) = f compose fa
}
scalaz中,单子的设计如下:
trait Monad[M[_]] {
  def pure[A](a: A): M[A] //given a value, you can lift it into the monad
  def bind[A, B](ma: M[A], f: A => B): M[B]
}

这可能并不特别明显有什么用,但事实证明它非常有用。我发现Daniel Spiewak的《单子不是隐喻》非常清晰地解释了为什么会这样,并且Tony Morris在通过reader单子进行配置方面的东西,是关于在单子中编写程序所指的内容的很好的实际示例。


21
不久之前,我写了这篇文章:http://gabrielsw.blogspot.com/2011/08/functors-applicative-functors-and.html (虽然我不是专家)。
首先要理解的是类型 ' T[X] ' :它是一种“上下文”(用类型来编码很有用,这样你就可以“组合”它们)。但是请看其他答案:)
好了,现在你有了一个带有上下文的类型,比如 M[A](A “在” M 中),并且你有一个普通函数 f:A=>B …… 你不能直接应用它,因为该函数期望 A,而你有 M[A]。你需要某种方式来“解包” M 的内容、应用函数并再次“打包”它。如果你对 M 的内部有“亲密”的了解,你可以这样做;如果你将其泛化成特质,你就会得到……
trait Functor[T[_]]{
  def fmap[A,B](f:A=>B)(ta:T[A]):T[B]
}

这就是函数子的作用,通过应用f函数将T[A]转换为T[B]。
Monad是一种神秘的生物,有着不同的隐喻,但一旦掌握了应用函子,就会发现它其实很容易理解。
函子使我们能够将函数应用于带有上下文的事物。但如果我们想要应用的函数已经在上下文中呢?(如果您有需要使用多个参数的函数,那么很容易陷入这种情况。)
现在我们需要像函子一样的东西,但是它还可以接受已经在上下文中的函数,并将它们应用到上下文中的元素上。这就是应用函子的作用。下面是其签名:
trait Applicative[T[_]] extends Functor[T]{
  def pure[A](a:A):T[A]
  def <*>[A,B](tf:T[A=>B])(ta:T[A]):T[B]
}

到目前为止,一切都很好。 现在来谈谈单子:如果现在你有一个将事物置于上下文中的函数呢?它的签名将是g:X=>M[X] ... 你不能使用函子,因为它期望X=>Y,所以我们最终会得到M[M[X]],你也不能使用应用函子,因为它期望已经在上下文M[X=>Y]中的函数。

因此,我们使用一个单子,它接受一个函数X=>M[X]和已经在上下文M[A]中的东西,并将函数应用于上下文中的内容,将结果打包在一个上下文中。签名如下:

trait Monad[M[_]] extends Applicative[M]{
  def >>=[A,B](ma:M[A])(f:A=>M[B]):M[B]
}

这可能有点抽象,但如果你思考如何使用"Option"来组合函数X=>Option[X],就能看到它的实际应用。

补充说明:重要的一点是将它们联系起来的符号>>=在Scala中被称为bind,也被称为flatMap。(此外,值得一提的是,有一些法则可以让函子、应用函子和单子正常工作)。


很好的解释。谢谢。 - Chaitanya

17

详细解释这两个概念的最佳文章是来自Eric Torreborre's Blog的"The Essence of the Iterator Pattern"。

Functor

trait Functor[F[_]] {
  def fmap[A, B](f: A => B): F[A] => F[B]
}
  • 解释Functor的一种方式是将其描述为类型A的值的计算。
    例如:
    • List[A] 是返回多个类型A值的计算(非确定性计算),
    • Option[A] 用于你可能有或可能没有的计算,
    • Future[A] 是一个稍后获取类型A值的计算,等等。
  • 另一种形象的理解方式是,它是类型A值的“某种容器”。

这是定义以下内容的基本层:

  • PointedFunctor(创建类型F[A]的值)和
  • Applic(提供方法applic,是容器F(F[A => B])中的计算值,适用于值F[A]),Applicative Functor(将ApplicPointedFunctor聚合在一起)。

这三个元素被用于定义Monad


bind怎么样?为了成为Monad,除了成为一个Applicative Functor之外,您还需要使用bindjoin。您可能知道bindflatMap - Dan Burton
@DanBurton: 它应该像在Scalaz Bind对象中声明的那样: http://scalaz.github.com/scalaz/scalaz-2.9.1-6.0.2/doc.sxr/scalaz/Bind.scala.html:`trait Bind[Z[_]] { def bind[A, B](a: Z[A], f: A => Z[B]): Z[B] }。更详细地,在定义了Apply之后定义Bind`:https://github.com/retronym/scalaz7-experimental/blob/master/core/src/main/scala/scalaz/Bind.scala - VonC
自己注意:使用类型成员而不是类型参数来定义相同类型(FunctorPointedBindMonad):https://gist.github.com/1458981。请参阅http://permalink.gmane.org/gmane.comp.lang.scala/25185。 - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接