【问题标题】:What exactly makes Option a monad in Scala?究竟是什么让 Option 成为 Scala 中的单子?
【发布时间】:2014-10-11 05:49:03
【问题描述】:

我知道 monad 是什么以及如何使用它们。我不明白的是什么,比如说,Option 一个单子?

在 Haskell 中,monad Maybe 是一个 monad,因为它是从 Monad 类实例化的(它至少有两个必要的函数 returnbind 使得类 Monad 确实是一个 monad)。

但在 Scala 中,我们有这个:

sealed abstract class Option[+A] extends Product with Serializable { ... }
trait Product extends Any with Equals { ... }

与 monad 无关。

如果我在 Scala 中创建自己的类,默认情况下它会是一个 monad 吗?为什么不呢?

【问题讨论】:

    标签: scala haskell functional-programming monads maybe


    【解决方案1】:

    Monad 是一个概念,如果你愿意的话,它是一个抽象接口,它简单地定义了一种组合数据的方式。

    Option 支持通过flatMap 进行合成,这几乎就是佩戴“monad 徽章”所需的一切。

    从理论上讲,还应该:

    • 支持unit 操作(return,在 Haskell 术语中)以从裸值创建 monad,在 Option 的情况下是 Some 构造函数
    • 尊重monadic laws

    但这并不是 Scala 严格执行的。

    Scala 中的 Monad 是一个比 Haskell 中松散得多的概念,而且这种方法更实用。 从语言的角度来看,monad 唯一相关的是用于理解的能力。

    flatMap是基本要求,您可以可选提供mapwithFilterforeach

    但是,没有像在 Haskell 中那样严格遵守 Monad 类型类。

    这是一个例子:让我们定义自己的 monad。

    class MyMonad[A](value: A) {
      def map[B](f: A => B) = new MyMonad(f(value))
      def flatMap[B](f: A => MyMonad[B]) = f(value)
      override def toString = value.toString
    }
    

    如您所见,我们只实现了mapflatMap(好吧,还有toString 作为商品)。 恭喜,我们有一个单子!让我们试试吧:

    scala> for {
      a <- new MyMonad(2)
      b <- new MyMonad(3)
    } yield a + b
    // res1: MyMonad[Int] = 5
    

    不错!我们没有做任何过滤,所以我们不需要实现withFilter。此外,由于我们正在产生一个值,我们也不需要foreach。基本上你可以实现任何你想支持的东西,没有严格的要求。如果您尝试在 for-comprehension 中进行过滤,但您尚未实现 withFilter,您只会收到编译时错误。

    【讨论】:

    • 所以 Option 不知道它本身就是一个 monad?与 Haskell 中的 Maybe 不同。
    • 准确地说,在 Scala 中,所有具有平面图的东西都被视为“monad”,它可以用于 for-comprehensions(相当于 haskell 中的 do-block)
    • "所以 Option 不知道它本身是一个 monad?不像 Haskell 中的 Maybe。"在 Haskell 中,Monad 只是一个类。仅仅为 Monad 定义一个类型类并不足以成为真正的 monad。必须正确定义所需的功能。例如,如果我为自己的类型 Foo 创建了一个类型类 Monad,但搞砸了 bind 的实现,那么 Foo 就不是 monad。在 Scala 中创建等效的 Monad 类是完全可能的——我认为它实际上是在 Scalaz 中完成的。
    • @J.Abrahamson 通过使用隐式参数模拟类型类。与此类似:gist.github.com/igstan/66e9bfd31d6ff03b233d.
    • @J.Abrahamson 我的 Haskell 有点生疏,所以我不确定我的量词是否正确,但我认为这是等价的:def quux[M[_], A](implicit m: Monad[M]): M[A]
    【解决方案2】:

    任何通过鸭子类型(部分)实现FilterMonadic trait 的东西都被认为是Scala 中的一个monad。这与在 Haskell 或 Monad typeclass in scalaz 中表示 monad 的方式不同。但是,为了使用 Scala 中的 for 理解语法糖,对象必须公开 FilterMonadic 特征中定义的一些方法。

    此外,在 Scala 中,Haskell return 函数的等效项是 yield 关键字,用于从 for 推导式中生成值。 yield 的脱糖是对“monad”的map 方法的调用。

    【讨论】:

    • 但是 Option 没有实现 FilterMonadic。在 Scala 中进行鸭式打字是什么意思? Duck-typing 是关于动态类型语言的。
    • 这意味着编译器将 for 理解视为一种宏,并将其替换为对 flatMapmapforeach 和/或 withFilter 的调用。类型检查是在这个翻译上执行的,而不是在 for comprehension 上。这就是为什么我说这是鸭式打字。任何看起来像 FilterMonadic 的东西都会被 for comprehension 接受。
    • 另一种表述可能是 for 理解是无类型的。也就是说,在扩展之前。
    【解决方案3】:

    我的说法是,作为 设计模式一流抽象 的 monad 之间出现了新的区别。 Haskell 有后者,形式为Monad 类型类。但是,如果您有一个具有(或可以实现)monad 操作并遵守法律的类型,那么它也是一个 monad。

    如今,您可以将 monad 视为 Java 8 库中的一种设计模式。 Java 8 中的 OptionalStream 类型带有对应于 Haskell return 的静态 of 方法和 flatMap 方法。但是没有Monad 类型。

    正如 Ionuț G. Stan 的回答所指出的那样,介于两者之间的还有“鸭式”方法。 C# 也有这一点——LINQ 语法不依赖于特定类型,而是可以与实现某些方法的任何类一起使用。

    【讨论】:

    • 其实在Java中不可能Monad类型,因为Monad是更高种类的,但是Java没有更高种类的参数多态性.
    • @JörgWMittag:正确。参见例如this earlier answer of mine to another question.
    • Optional 不应该被视为真正的 monad 的另一个原因 - 如果您仔细阅读 Java 8 文档,他们声明 Optional 应该只用作一种方法,而不是任何状态的类型。这实际上是一个相当严重的缺点,不幸的是没有明显的方法可以解决它。
    【解决方案4】:

    Scala 本身不提供 monad 的概念。您可以将 monad 表示为类型类,但 Scala 也不提供类型类的概念。但猫确实如此。因此,您可以使用必要的样板在 Scala 中创建 Monad,例如巧妙地使用了特征和隐含,或者您可以使用提供开箱即用的单子特征的猫。作为比较,Haskel 提供了单子作为语言的一部分。关于您的具体问题,Option 可以表示为 monad,因为它具有 flatMap 方法和 unit 方法(例如,将值包装在 Some 或 Future 中)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-12
      • 2014-10-28
      • 2012-08-27
      • 2010-11-12
      • 2011-03-18
      • 2011-01-22
      相关资源
      最近更新 更多