Оказывается, что обычный способ кодирования классов типов в Scala довольно близко следует за Haskell: List
не реализует Monad
интерфейс (как вы могли ожидать от объектно-ориентированного языка), а мы определяем экземпляр класса типа в отдельном объекте. .
trait Monad[M[_]] {
def point[A](a: => A): M[A]
def bind[A, B](ma: M[A])(f: A => M[B]): M[B]
def map[A, B](ma: M[A])(f: A => B): M[B] = bind(ma)(a => point(f(a)))
}
implicit object listMonad extends Monad[List] {
def point[A](a: => A) = List(a)
def bind[A, B](ma: List[A])(f: A => List[B]) = ma flatMap f
}
Эта идея представлена в Типовых классах для бедняков и исследована подробнее глубоко в Типовые классы как объекты и следствия. Обратите внимание, что метод point
мог не быть определен в объектно-ориентированном интерфейсе, поскольку у него нет M[A]
в качестве одного из аргументов, который нужно преобразовать в ссылку this
в кодировке OO. (Или, другими словами: он не может быть частью интерфейса по той же причине, по которой подпись конструктора не может быть представлена в интерфейсе.)
Затем вы можете написать liftM2
как:
def liftM2[M[_], A, B, C](f: (A, B) => C)
(implicit M: Monad[M]): (M[A], M[B]) => M[C] =
(ma, mb) => M.bind(ma)(a => M.map(mb)(b => f(a, b)))
val f = liftM2[List, Int, Int, Int](_ + _)
f(List(1, 2, 3), List(4, 5)) // List(5, 6, 6, 7, 7, 8)
Этот шаблон широко применяется в Scalaz. Версия 7, которая сейчас находится в разработке, включает индекс типовые классы.
Помимо предоставления классов и экземпляров типов для стандартных библиотечных типов, он предоставляет «синтаксический» уровень, который позволяет использовать более знакомый стиль вызова методов Receiver.method (args). Это часто обеспечивает лучший вывод типов (с учетом алгоритма вывода слева направо в Scala) и позволяет использовать синтаксический сахар для понимания. Ниже мы используем это, чтобы переписать liftM2
на основе методов map
и flatMap
в MonadV
.
// Before Scala 2.10
trait MonadV[M[_], A] {
def self: M[A]
implicit def M: Monad[M]
def flatMap[B](f: A => M[B]): M[B] = M.bind(self)(f)
def map[B](f: A => B): M[B] = M.map(self)(f)
}
implicit def ToMonadV[M[_], A](ma: M[A])
(implicit M0: Monad[M]) =
new MonadV[M, A] {
val M = M0
val self = ma
}
// Or, as of Scala 2.10
implicit class MonadOps[M[_], A](self: M[A])(implicit M: Monad[M]) {
def flatMap[B](f: A => M[B]): M[B] = M.flatMap(self)(f)
def map[B](f: A => B): M[B] = M.map(self)(f)
}
def liftM2[M[_]: Monad, A, B, C](f: (A, B) => C): (M[A], M[B]) => M[C] =
(ma, mb) => for {a <- ma; b <- mb} yield f(a, b)
Обновить
Да, можно написать менее общую версию liftM2
для коллекций Scala. Вам просто нужно накормить все необходимые CanBuildFrom
экземпляры.
scala> def liftM2[CC[X] <: TraversableLike[X, CC[X]], A, B, C]
| (f: (A, B) => C)
| (implicit ba: CanBuildFrom[CC[A], C, CC[C]], bb: CanBuildFrom[CC[B], C, CC[C]])
| : (CC[A], CC[B]) => CC[C] =
| (ca, cb) => ca.flatMap(a => cb.map(b => f(a, b)))
liftM2: [CC[X] <: scala.collection.TraversableLike[X,CC[X]], A, B, C](f: (A, B) => C)(implicit ba: scala.collection.generic.CanBuildFrom[CC[A],C,CC[C]], implicit bb: scala.collection.generic.CanBuildFrom[CC[B],C,CC[C]])(CC[A], CC[B]) => CC[C]
scala> liftM2[List, Int, Int, Int](_ + _)
res0: (List[Int], List[Int]) => List[Int] = <function2>
scala> res0(List(1, 2, 3), List(4, 5))
res1: List[Int] = List(5, 6, 6, 7, 7, 8)
person
retronym
schedule
03.12.2011
instance
в Haskell (и самое надежное последнее средство, когда другие решения Scalaish терпят неудачу). - person Owen   schedule 03.12.2011