Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>The usual way to encode type classes in Scala turns out to follow Haskell pretty closely: <code>List</code> doesn't implement a <code>Monad</code> interface (as you might expect in an object-oriented language), but rather we define the type class instance in a separate object.</p> <pre><code>trait Monad[M[_]] { def point[A](a: =&gt; A): M[A] def bind[A, B](ma: M[A])(f: A =&gt; M[B]): M[B] def map[A, B](ma: M[A])(f: A =&gt; B): M[B] = bind(ma)(a =&gt; point(f(a))) } implicit object listMonad extends Monad[List] { def point[A](a: =&gt; A) = List(a) def bind[A, B](ma: List[A])(f: A =&gt; List[B]) = ma flatMap f } </code></pre> <p>This idea is introduced in <a href="http://lampwww.epfl.ch/~odersky/talks/wg2.8-boston06.pdf" rel="nofollow">Poor Man's Type Classes</a> and explored more deeply in <a href="http://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf" rel="nofollow">Type Classes as Objects and Implicits</a>. Notice that the <code>point</code> method could <em>not</em> have been defined in an object-oriented interface, as it doesn't have <code>M[A]</code> as one of it's arguments to be converted to the <code>this</code> reference in an OO encoding. (Or put another way: it can't be part of an interface for the same reason a constructor signature can't be represented in an interface.)</p> <p>You can then write <code>liftM2</code> as:</p> <pre><code>def liftM2[M[_], A, B, C](f: (A, B) =&gt; C) (implicit M: Monad[M]): (M[A], M[B]) =&gt; M[C] = (ma, mb) =&gt; M.bind(ma)(a =&gt; M.map(mb)(b =&gt; 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) </code></pre> <p>This pattern has been applied extensively in <a href="http://scalaz.org" rel="nofollow">Scalaz</a>. <a href="https://github.com/scalaz/scalaz/tree/scalaz-seven" rel="nofollow">Version 7</a>, currently in development, includes an <a href="http://jenkins.scala-tools.org/view/scalaz/job/scalaz-seven/lastSuccessfulBuild/artifact/target/scala-2.9.1/unidoc/index.html#scalaz.package" rel="nofollow">index of the type classes</a>. </p> <p>In addition to providing type classes and instances for standard library types, it provides a 'syntactic' layer that allows the more familiar <em>receiver.method(args)</em> style of method invocation. This often affords better type inference (accounting for Scala's left-to-right inference algorithm), and allows use of the for-comprehension syntactic sugar. Below, we use that to rewrite <code>liftM2</code>, based on the <code>map</code> and <code>flatMap</code> methods in <code>MonadV</code>.</p> <pre><code>// Before Scala 2.10 trait MonadV[M[_], A] { def self: M[A] implicit def M: Monad[M] def flatMap[B](f: A =&gt; M[B]): M[B] = M.bind(self)(f) def map[B](f: A =&gt; 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 =&gt; M[B]): M[B] = M.flatMap(self)(f) def map[B](f: A =&gt; B): M[B] = M.map(self)(f) } def liftM2[M[_]: Monad, A, B, C](f: (A, B) =&gt; C): (M[A], M[B]) =&gt; M[C] = (ma, mb) =&gt; for {a &lt;- ma; b &lt;- mb} yield f(a, b) </code></pre> <p><strong>Update</strong></p> <p>Yep, its possible to write less generic version of <code>liftM2</code> for the Scala collections. You just have to feed in all the required <code>CanBuildFrom</code> instances.</p> <pre><code>scala&gt; def liftM2[CC[X] &lt;: TraversableLike[X, CC[X]], A, B, C] | (f: (A, B) =&gt; C) | (implicit ba: CanBuildFrom[CC[A], C, CC[C]], bb: CanBuildFrom[CC[B], C, CC[C]]) | : (CC[A], CC[B]) =&gt; CC[C] = | (ca, cb) =&gt; ca.flatMap(a =&gt; cb.map(b =&gt; f(a, b))) liftM2: [CC[X] &lt;: scala.collection.TraversableLike[X,CC[X]], A, B, C](f: (A, B) =&gt; 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]) =&gt; CC[C] scala&gt; liftM2[List, Int, Int, Int](_ + _) res0: (List[Int], List[Int]) =&gt; List[Int] = &lt;function2&gt; scala&gt; res0(List(1, 2, 3), List(4, 5)) res1: List[Int] = List(5, 6, 6, 7, 7, 8) </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload