Filemon279
Filemon279

Reputation: 188

Scala polymorphic methods - { case x:T => x } returns all types

I do not understand why in Scala polymorphic method do not work as I expected.

lets have method:

def sth[T](list: Iterable[Any]): List[T] = {
list.collect({case s:T => s}).toList
}

not I would like to get only Boolean from my list. so I would write:

val out = sth[Boolean](List(true, false, 2.0, 2, 4, 12.3))

What I expect is List(true, false) and IntelIJ shows me that out is instance of: List[Boolean] BUT what this method returns me is my input list: List(true, false, 2.0, 2, 4, 12.3)

I also did try with list.filter(s => s.isInstanceOf[T]) but had same result.

enter image description here

I'm a little bit confused. Can u explain me what have I done wrong?

Upvotes: 1

Views: 128

Answers (1)

Mario Galic
Mario Galic

Reputation: 48430

Due to type erasure the check

case s:T => ...

becomes something like

if (s.isInstanceOf[Object]) ...

as indicated by the compiler warning message

 warning: abstract type pattern T is unchecked since it is eliminated by erasure
    list.collect({case s:T => s}).toList

Try providing ClassTag like so

import scala.reflect.ClassTag

def collect[T: ClassTag](list: Iterable[Any]): List[T] = {
  list.collect({ case s: T => s }).toList
}

collect[Boolean](List(true, false, 2.0, 2, 4, 12.3))
collect[Double](List(true, false, 2.0, 2, 4, 12.3))

which outputs

res0: List[Boolean] = List(true, false)
res1: List[Double] = List(2.0, 12.3)

which works due to some compiler magic explained here.

Here is our own type class solution

trait Collectable[T] {
  def collect(list: Iterable[Any]): List[T]
}

object Collectable {
  def collect[T](list: Iterable[Any])(implicit ev: Collectable[T]): List[T] =
    ev.collect(list)

  implicit val booleanCollect: Collectable[Boolean]  = 
    (list: Iterable[Any]) => list.collect({ case s: Boolean => s }).toList

  implicit val doubleCollect: Collectable[Double] = 
    (list: Iterable[Any]) => list.collect({ case s: Double => s }).toList
}

import Collectable._

collect[Boolean](List(true, false, 2.0, 2, 4, 12.3))
collect[Double](List(true, false, 2.0, 2, 4, 12.3))

which outputs

res0: List[Boolean] = List(true, false)
res1: List[Double] = List(2.0, 12.3)

Upvotes: 8

Related Questions