Ответ 1
Как сказал Ørjan Johansen, Scala не поддерживает отправку метода по возвращаемому типу. Объектная система Scala построена по JVM one, а инструкция 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"
Здесь, даже если переменная y
имеет тип A
, вызывается метод hello
из B
, потому что JVM "видит", что фактический тип объекта в y
равен B
и вызывает соответствующий метод.
Однако JVM принимает тип переменной, на которую метод учитывается. Например, невозможно вызвать разные методы, основанные на типе аргументов runtime без явных проверок. Например:
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"
Здесь мы имеем два метода с тем же именем, но с разными типами параметров. И даже если n
действительный тип Int
, вызывается hello(Number)
версия - он статически статично основан на статическом типе n
(эта функция, статическое разрешение на основе типов аргументов, называется перегрузкой). Следовательно, динамическая отправка на аргументы метода отсутствует. Некоторые языки также поддерживают отправку аргументов метода, например, общие методы Lisp CLOS или Clojure работают так.
Haskell имеет усовершенствованную систему типов (она сопоставима с Scala, и на самом деле они оба происходят из System 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
Затем, в зависимости от контекста, где вызывается метод, можно вызвать функцию read
для Integer
или Double
:
x :: Integer
x = read "12345" // read for Integer is called
y :: Double
y = read "12345.0" // read for Double is called
Это очень мощный метод, который не имеет соответствия в открытой объектной системе JVM, поэтому объектная система Scala тоже не поддерживает. Кроме того, отсутствие полномасштабного вывода типа сделало бы эту функцию несколько громоздкой в использовании. Таким образом, стандартная библиотека Scala не имеет метода return
/unit
в любом месте - невозможно выразить ее с помощью обычной объектной системы, просто нет места, где такой метод может быть определен. Следовательно, концепция монады в Scala является неявной и условной - все с соответствующим методом flatMap
можно считать монадой, и все с правильными методами можно использовать в конструкции for
. Это очень похоже на утиную печать.
Однако система типа Scala вместе со своим механизмом implicits достаточно мощна, чтобы выражать полнофункциональные классы типов и, как правило, общие монады формальным образом, хотя из-за трудностей с полным типом вывода может потребоваться добавить больше типа, чем в Haskell.
Это определение класса типа monad в Scala:
trait Monad[M[_]] {
def unit[A](a: A): M[A]
def bind[A, B](ma: M[A])(f: A => M[B]): M[B]
}
И это его реализация для 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)
}
Затем это можно использовать общим способом следующим образом:
// note M[_]: Monad context bound
// this is a port of Haskell 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))
Здесь filterM
записывается в общем случае для любого экземпляра класса типа Monad
. Поскольку неявный объект OptionMonad
присутствует на сайте вызова filterM
, он будет передан в filterM
неявно, и он сможет использовать его методы.
Вы можете видеть сверху, что классы классов позволяют эмулировать отправку по типу возврата даже в Scala. Фактически, это именно то, что Haskell делает под обложками - и Scala, и Haskell пропускают словарь методов, реализующих некоторый тип класса, хотя в Scala он несколько более явный, потому что эти словари являются первоклассными объектами там и могут быть импортированы по требованию или даже переданы явно, поэтому на самом деле это не правильная диспетчеризация, поскольку она не встроена.
Если вам нужна эта сумма, вы можете использовать Scalaz библиотеку, которая содержит много типов классов (включая монаду) и их экземпляры для некоторых распространенных типов, включая Option
.