How to specify a type parameter in calling a funct

2019-08-14 12:02发布

问题:

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.

回答1:

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



回答2:

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{...}