为什么Scala没有为每个monad定义返回/单位函数(与Haskell相比)?

jhe*_*dus 28 monads haskell scala

在Scala中设计决策背后的原因是,monads没有返回/单位函数,而Haskell每个monad都有一个返回函数,它将值放入给定monad的标准monadic上下文中?

例如,为什么List,Option,Set等...没有在标准库中定义的返回/单位函数,如下面的幻灯片所示?

我问这个是因为在反应性课程中,Martin Odersky明确地提到了这个事实,如幻灯片中可以看到的那样,但是没有解释为什么Scala没有它们,即使单位/回归是monad的基本属性.

在此输入图像描述 在此输入图像描述 在此输入图像描述

Vla*_*eev 35

正如ØrjanJohansen所说,Scala不支持返回类型的方法调度.Scala对象系统是基于JVM构建的,而JVM invokevirtual指令是动态多态的主要工具,它根据this对象的类型调度调用.

作为旁注,调度是选择具体方法进行调用的过程.在Scala/Java中,所有方法都是虚方法,也就是说,调用的实际方法取决于对象的实际类型.

class A { def hello() = println("hello method in A") }

class B extends A { override def hello() = println("hello method in B") }

val x: A = new A
x.hello()  // prints "hello method in A"

val y: A = new B
y.hello()  // prints "hello method in B"
Run Code Online (Sandbox Code Playgroud)

这里,即使y变量是类型A,也调用hello方法from B,因为JVM"看到"对象的实际类型yB并调用适当的方法.

但是,JVM仅考虑调用该方法的变量的类型.例如,不可能在没有显式检查的情况下根据参数的运行时类型调用不同的方法.例如:

class A {
  def hello(x: Number) = println(s"Number: $x")
  def hello(y: Int) = println(s"Integer: $y")
}

val a = new A
val n: Number = 10: Int
a.hello(n)  // prints "Number: 10"
Run Code Online (Sandbox Code Playgroud)

这里我们有两个具有相同名称但具有不同参数类型的方法.即使n是实际类型Int,hello(Number)也调用了版本 - 它是基于n静态变量类型静态解析的(此功能,基于参数类型的静态解析称为重载).因此,方法参数没有动态调度.有些语言也支持对方法参数进行调度,例如,Common Lisp的CLOS或Clojure的多方法就像这样工作.

Haskell有先进的类型系统(这相当于把Scala的,事实上他们都来源于F系统,但Scala的类型系统支持的子类型,这使得类型推断要困难得多),这使得全球的类型推断,至少,不支持某些扩展.Haskell也有类型类的概念,它是动态多态的工具.类型类可以被宽泛地认为是没有继承的接口,但是对参数和返回值类型进行调度.例如,这是一个有效的类型类:

class Read a where
    read :: String -> a

instance Read Integer where
    read s = -- parse a string into an integer

instance Read Double where
    read s = -- parse a string into a double
Run Code Online (Sandbox Code Playgroud)

然后,根据调用方法的上下文,read函数IntegerDouble可以调用:

x :: Integer
x = read "12345"  // read for Integer is called

y :: Double
y = read "12345.0"  // read for Double is called
Run Code Online (Sandbox Code Playgroud)

这是一种非常强大的技术,在裸JVM对象系统中没有对应关系,因此Scala对象系统也不支持它.此外,缺乏全尺寸类型推断将使该特征使用起来有些麻烦.因此,Scala标准库在任何地方都没有return/ unit方法 - 使用常规对象系统无法表达它,根本没有可以定义这种方法的地方.因此,Scala中的monad概念是隐含的和传统的 - 使用适当flatMap方法的所有内容都可以被视为monad,并且使用正确方法的所有内容都可以用于for构造.这很像鸭子打字.

然而,Scala的类型系统以其implicits机制一起是足够强大的表达功能齐全型类,以及由此延伸,以正式的方式通用的单子,但由于全类型推断的困难,可能需要比Haskell中增加更多类型的注释.

这是Scala中monad类型类的定义:

trait Monad[M[_]] {
  def unit[A](a: A): M[A]
  def bind[A, B](ma: M[A])(f: A => M[B]): M[B]
}
Run Code Online (Sandbox Code Playgroud)

这是它的实现Option:

implicit object OptionMonad extends Monad[Option] {
  def unit[A](a: A) = Some(a)
  def bind[A, B](ma: Option[A])(f: A => Option[B]): Option[B] =
    ma.flatMap(f)
}
Run Code Online (Sandbox Code Playgroud)

然后这可以像这样的通用方式使用:

// note M[_]: Monad context bound
// this is a port of Haskell's filterM found here:
// http://hackage.haskell.org/package/base-4.7.0.1/docs/src/Control-Monad.html#filterM
def filterM[M[_]: Monad, A](as: Seq[A])(f: A => M[Boolean]): M[Seq[A]] = {
  val m = implicitly[Monad[M]]
  as match {
    case x +: xs =>
      m.bind(f(x)) { flg =>
        m.bind(filterM(xs)(f)) { ys =>
          m.unit(if (flg) x +: ys else ys)
        }
      }
    case _ => m.unit(Seq.empty[A])
  }
}

// using it

def run(s: Seq[Int]) = {
  import whatever.OptionMonad  // bring type class instance into scope

  // leave all even numbers in the list, but fail if the list contains 13
  filterM[Option, Int](s) { a =>
    if (a == 13) None
    else if (a % 2 == 0) Some(true)
    else Some(false)
  }
}

run(1 to 16)  // returns None
run(16 to 32)  // returns Some(List(16, 18, 20, 22, 24, 26, 28, 30, 32))
Run Code Online (Sandbox Code Playgroud)

这里filterM一般是针对Monad类型类的任何实例编写的.因为OptionMonad隐式对象存在于filterM调用站点,所以它将被filterM隐式传递,并且它将能够使用它的方法.

您可以从上面看到类型类允许在返回类型上模拟调度,即使在Scala中也是如此.事实上,这正是哈斯克尔确实在幕后 - 既斯卡拉和Haskell是路过的实现某种类型的类方法字典,虽然在斯卡拉它是有点更明确,因为这些"字典"是第一类对象存在,可以是按需导入或甚至明确传递,因此它不是真正适当的调度,因为它不是嵌入式的.

如果你需要这么多的通用性,你可以使用Scalaz库,它包含很多类型类(包括monad)和它们的一些常见类型的实例,包括Option.


Cyä*_*gha 9

我不认为你真的在说Scala的monad没有单位函数 - 而是单位函数的名称可能会有所不同.这就是第二张幻灯片中的例子.

至于为什么会这样,我认为这只是因为Scala在JVM上运行,并且这些函数必须作为JVM方法实现 - 这些方法由以下各项唯一标识:

  • 他们所属的阶级;
  • 他们的名字;
  • 他们的参数类型.但他们的返回类型并未确定.由于参数类型通常不会区分各种单元函数(它通常只是泛型类型),因此需要不同的名称.

实际上,它们通常被实现为apply(x)monad类的伴随对象上的方法.例如,对于类List,单位函数是apply(x)对象上的方法List.按照惯例,List.apply(x)也可以称之为List(x)更常见/惯用.

所以我猜Scala至少有一个单元函数的命名约定,虽然它没有唯一的名称:

// Some monad :
class M[T] {
  def flatMap[U](f: T => M[U]): M[U] = ???
}
// Companion object :
object M {
  def apply(x: T): M[T] = ??? // Unit function
}

// Usage of the unit function :
val x = ???
val m = M(x)
Run Code Online (Sandbox Code Playgroud)


Chr*_*tin 5

警告:我还在学习Haskell,我正在努力解决这个问题.


首先,你已经知道的 - Haskell的do符号desugars 绑定:

从维基百科借用这个例子:

add mx my = do
  x <- mx
  y <- my
  return (x + y)

add mx my =
  mx >>= (\x ->
    my >>= (\y ->
      return (x + y)))
Run Code Online (Sandbox Code Playgroud)

Scala的类比dofor-yield表达式.它同样将每一步都贬低flatMap(相当于bind).

然而,有一个不同之处:最后<-一个屈服于desgoars map,而不是flatMap.

def add(mx: Option[Int], my: Option[Int]) =
  for {
    x <- mx
    y <- my
  } yield x + y

def add(mx: Option[Int], my: Option[Int]) =
  mx.flatMap(x =>
    my.map(y =>
      x + y))
Run Code Online (Sandbox Code Playgroud)

因此,由于您在最后一步没有"展平",表达式值已经具有monad类型,因此不需要使用与之类似的东西"重新包装"它return.