I am experimenting with a set of custom container functions and took inspiration from Scala's collections library with regard to the CanBuildFrom[-From, -Elem, -To]
implicit parameter.
In Scala's collections, CanBuildFrom
enables parametrization of the return type of functions like map
. Internally the CanBuildFrom
parameter is used like a factory: map
applies it's first order function on it's input elements, feeds the result of each application into the CanBuildFrom
parameter, and finally calls CanBuildFrom's result
method which builds the final result of the map
call.
In my understanding the -From
type parameter of CanBuildFrom[-From, -Elem, -To]
is only used in apply(from: From): Builder[Elem, To]
which creates a Builder
preinitialized with some elements. In my custom container functions I don't have that use case, so I created my own CanBuildFrom
variant Factory[-Elem, +Target]
.
Now I can have a trait Mappable
trait Factory[-Elem, +Target] {
def apply(elems: Traversable[Elem]): Target
def apply(elem: Elem): Target = apply(Traversable(elem))
}
trait Mappable[A, Repr] {
def traversable: Traversable[A]
def map[B, That](f: A => B)(implicit factory: Factory[B, That]): That =
factory(traversable.map(f))
}
and an implementation Container
object Container {
implicit def elementFactory[A] = new Factory[A, Container[A]] {
def apply(elems: Traversable[A]) = new Container(elems.toSeq)
}
}
class Container[A](val elements: Seq[A]) extends Mappable[A, Container[A]] {
def traversable = elements
}
Unfortunately though, a call to map
object TestApp extends App {
val c = new Container(Seq(1, 2, 3, 4, 5))
val mapped = c.map { x => 2 * x }
}
yields an error message not enough arguments for method map: (implicit factory: tests.Factory[Int,That])That. Unspecified value parameter factory
. The error goes away when I add an explicit import import Container._
or when I explicitly specify the expected return type val mapped: Container[Int] = c.map { x => 2 * x }
All of these "workarounds" become unnecessary when I add an unused third type parameter to Factory
trait Factory[-Source, -Elem, +Target] {
def apply(elems: Traversable[Elem]): Target
def apply(elem: Elem): Target = apply(Traversable(elem))
}
trait Mappable[A, Repr] {
def traversable: Traversable[A]
def map[B, That](f: A => B)(implicit factory: Factory[Repr, B, That]): That =
factory(traversable.map(f))
}
and change the implicit definition in Container
object Container {
implicit def elementFactory[A, B] = new Factory[Container[A], B, Container[B]] {
def apply(elems: Traversable[A]) = new Container(elems.toSeq)
}
}
So finally my question: Why is the seemingly unused -Source
type parameter necessary to resolve the implicit?
And as an additional meta question: How do you solve these kinds of problems if you don't have a working implementation (the collection library) as a template?
Clarification
It might be helpful to explain why I thought the implicit resolution should work without the additional -Source
type parameter.
According to this doc entry on implicit resolution, implicits are looked up in companion objects of types. The author does not provide an example for implicit parameters from companion objects (he only explains implicit conversion) but I think what this means is that a call to Container[A]#map
should make all implicits from object Container
available as implicit parameters, including my elementFactory
. This assumption is supported by the fact that it is enough to provide the target type (no additional explicit imports!!) to get the implicit resolved.
From
is to guide implicit selection. We want"foo".map(_.toUpper)
andList('f', 'o', 'o').map(_.toUpper)
to have different return types, so they need to receive different instances. – Oryx