Reputation: 3838
I have a class defined like this:
implicit class TraversableLikeView[+A, +Repr, Raw](self: Raw)(implicit cast: Raw => TraversableLike[A,Repr]) {
def filterByType[B, That](implicit bf: CanBuildFrom[Repr, B, That]): That = {
val result = cast(self).flatMap{
case tt: B => Some(tt)
case _ => None
}(bf)
result
}
}
When calling it on TraversableLikeView("abc" :: "def" :: Nil)
:
I want type parameter B
to be specified, and type parameter That
to be automatically inferred from predefined implicits. So I call the function like this:
TraversableLikeView("abc" :: "def" :: Nil).filterByType[String, _].foreach{...}
However, the compiler gave me this error:
Error:(38, 56) unbound wildcard type
....filterByType[String, _].foreach{...
^
Why scala is unable to infer this type parameter? What should I do to fix it?
UPDATE: The closest thing I can get is like the following:
implicit class TraversableLikeView[A, Repr, Raw <% TraversableLike[A, Repr]](self: Raw) {
def filterByType[B] = new FilterByType[B]
class FilterByType[B] {
def apply[That](implicit bf: CanBuildFrom[Repr, B, That]): That = {
val result = self.flatMap{
case tt: B => Some(tt)
case _ => None
}(bf)
result
}
}
}
test("1") {
val res: Seq[String] = Seq("abc", "def").filterByType[String].apply
println(res)
val res2: Array[String] = Array("abc", "def").filterByType[String].apply
println(res2)
val res3: Set[String] = Set("abc", "def").filterByType[String].apply
println(res3)
}
However the compiler seems to fail on finding the evidence (which shouldn't even be needed):
Error:(38, 33) value filterByType is not a member of Array[com.tribbloids.spookystuff.pages.PageLike]
val unfetched = pageLikes.filterByType[Unfetched].head
^
If I drop the view bound it will work perfectly (of course except on Array[String]), but I'm kind of surprised to see that it take such circumvention to implemennt a simple thing in scala.
Upvotes: 1
Views: 93
Reputation: 170735
One solution to this problem is to split your type parameters between two methods:
class FilterByType[B] {
def apply[That](implicit bf: CanBuildFrom[Repr, B, That]): That = {
val result = cast(self).flatMap{
case tt: B => Some(tt)
case _ => None
}(bf)
result
}
}
def filterByType[B] = new FilterByType[B]
TraversableLikeView("abc" :: "def" :: Nil).filterByType[String].apply.foreach{...}
Upvotes: 1
Reputation: 5556
You cannot specify only one of the two type parameters and let the other one be inferred. You either omit the types or specify them both.
Do you really need to use TraversableLike and view bounds? How about just using the magnet pattern like this:
trait FilterMagnet[A, B] {
type Out
def apply(self: Traversable[A]): Out
}
object FilterMagnet {
implicit def forThat[A, B: ClassTag, That](implicit bf: CanBuildFrom[Traversable[A], B, That]) = new FilterMagnet[A, B] {
type Out = That
def apply(self: Traversable[A]): That = self.collect { case tt: B => tt }
}
}
implicit class TraversableLikeView[A, C[A0 <: A] <: Traversable[A0]](self: C[A]) {
def filterByType[B](implicit magnet: FilterMagnet[A, B]): magnet.Out = magnet(self)
}
val listResult = TraversableLikeView("abc" :: "def" :: 3 :: Nil).filterByType[String]
println(listResult) // prints List(abc, def)
val setResult = TraversableLikeView(Set("abc", 3, true, "def")).filterByType[String]
println(setResult) // prints Set(abc, def)
Don't forget to add a ClassTag
context bound to be able to filter by B
, otherwise type erasure doesn't let you keep at runtime information about the B
type
Upvotes: 1