La façon habituelle de coder les classes de type de Scala s'avère à suivre Haskell à peu de choses près: List
ne pas mettre en œuvre un Monad
interface (comme vous pouvez vous attendre dans un langage orienté-objet), mais plutôt de définir le type de l'instance de classe dans un objet séparé.
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
}
Cette idée est introduite dans du Pauvre Type Classes et exploré plus profondément dans les Classes de Type en tant qu'Objets et Implicites. Notez que l' point
méthode pourrait ne pas avoir été définies dans une interface orientée objet, car il n'a pas M[A]
comme l'un de ses arguments pour être converti en this
référence dans un OO encodage. (Ou dans l'autre sens: il ne peut pas faire partie d'une interface pour la même raison, une signature du constructeur ne peut pas être représenté dans une interface.)
Vous pouvez ensuite écrire liftM2
comme:
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)
Ce modèle a été largement appliquées dans Scalaz. La Version 7, actuellement en développement, comprend un indice du type de classes.
En plus de fournir le type de classes et d'instances pour le standard de la bibliothèque de types, il procure un "syntaxique" couche qui permet les plus connues du récepteur.méthode(args) style de l'invocation de méthode. Souvent, cela permet de mieux l'inférence de type (de la comptabilité pour la Scala de gauche à droite algorithme d'inférence), et permet d'utiliser le pour-de la compréhension sucre syntaxique. Ci-dessous, nous utilisons que de réécrire liftM2
, basée sur l' map
et flatMap
méthodes MonadV
.
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
}
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)
Mise à jour
Yep, il est possible d'écrire moins de la version générique de liftM2
pour la Scala de collections. Vous avez juste à se nourrir dans tous les CanBuildFrom
des cas.
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)