My thoughts on the two approaches.
Structural Types
You can use a structural type for foreach
, but for map
it doesn't appear you can construct one to work across multiple types. For example:
import collection.generic.CanBuildFrom
object StructuralMap extends App {
type HasMapAndForeach[A] = {
// def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That
def foreach[B](f: (A) ⇒ B): Unit
}
def printValues(xs: HasMapAndForeach[Any]) {
xs.foreach(println _)
}
// def mapValues(xs: HasMapAndForeach[Any]) {
// xs.map(_.toString).foreach(println _)
// }
def forComp1(xs: HasMapAndForeach[Any]) {
for (i <- Seq(1,2,3)) println(i)
}
printValues(List(1,2,3))
printValues(Some(1))
printValues(Seq(1,2,3))
// mapValues(List(1,2,3))
}
scala> StructuralMap.main(new Array[String](0))
1
2
3
4
5
6
7
8
9
10
See the map
method commented out above, it has List
hardcoded as a type parameter in the CanBuildFrom
implicit. There might be a way to pick up the type generically - I will leave that as a question to the Scala type gurus out there. I tried substituting HasMapAndForeach
and this.type
for List
but neither of those worked.
The usual performance caveats about structural types apply.
Scalaz
Since structural types is a dead end if you want to support map
then let's look at the scalaz approach from Travis and see how it works. Here are his methods:
def printValues[F[_]: Each](xs: F[Int]) = xs foreach println
def incremented[F[_]: Functor](xs: F[Int]) = xs map (_ + 1)
(In the below correct me if I am wrong, I am using this as a scalaz learning experience)
The typeclasses Each
and Functor
are used to restrict the types of F
to ones where implicits are available for Each[F]
or Functor[F]
, respectively. For example, in the call
printValues(List(1,2,3))
the compiler will look for an implicit that satisfies Each[List]
. The Each
trait is
trait Each[-E[_]] {
def each[A](e: E[A], f: A => Unit): Unit
}
In the Each
object there is an implicit for Each[TraversableOnce]
(List
is a subtype of TraversableOnce
and the trait is contravariant):
object Each {
implicit def TraversableOnceEach[A]: Each[TraversableOnce] = new Each[TraversableOnce] {
def each[A](e: TraversableOnce[A], f: A => Unit) = e foreach f
}
}
Note that the "context bound" syntax
def printValues[F[_]: Each](xs: F[Int])
is shorthand for
def printValues(xs: F[Int])(implicit ev: Each[F])
Both of these denote that F
is a member of the Each
typeclass. The implicit that satisfies the typeclass is passed as the ev
parameter to the printValues
method.
Inside the printValues
or incremented
methods the compiler doesn't know that xs
has a map
or foreach
method because the type parameter F
doesn't have any upper or lower bounds. As far as it can tell F
is AnyRef
and satisfies the context bound (is part of the typeclass). What is in scope that does have foreach
or map
? MA
from scalaz has both foreach
and map
methods:
trait MA[M[_], A] {
def foreach(f: A => Unit)(implicit e: Each[M]): Unit = e.each(value, f)
def map[B](f: A => B)(implicit t: Functor[M]): M[B] = t.fmap(value, f)
}
Note that the foreach
and map
methods on MA
are constrained by the Each
or Functor
typeclass. These are the same constraints from the original methods so the constraints are satisfied and an implicit conversion to MA[F, Int]
takes place via the maImplicit
method:
trait MAsLow extends MABLow {
implicit def maImplicit[M[_], A](a: M[A]): MA[M, A] = new MA[M, A] {
val value = a
}
}
The type F
in the original method becomes type M
in MA
.
The implicit parameter that was passed into the original call is then passed as the implicit parameter into foreach
or map
. In the case of foreach
, each
is called on its implicit parameter e
. In the example from above the implicit ev
was type Each[TraversableOnce]
because the original parameter was a List
, so e
is the same type. foreach
calls each
on e
which delegates to foreach
on TraversableOnce
.
So the order of calls for printValues(List(1,2,3))
is:
new Each[TraversableOnce]
-> printValues
-> new MA
-> MA.foreach
-> Each.each
-> TraversableOnce.foreach
As they say, there is no problem that can't be solved with an extra level of indirection :)