Function which generically takes a type and returns the same type
-
29-05-2021 - |
Question
I am having a tough time understanding why the Scala compiler is unhappy about this function definition:
def trimNonWordCharacters[T <: Iterable[String]](items: T): T =
items map { _.replaceAll("\\W", "") }
Here is the REPL output:
scala> def trimNonWordCharacters[T <: Iterable[String]](items: T): T =
items map { _.replaceAll("\\W", "") }
<console>:5: error: type mismatch;
found : Iterable[java.lang.String]
required: T
def trimNonWordCharacters[T <: Iterable[String]](items: T): T = items map { _.replaceAll("\\W", "") }
The goal is to pass in any implementation of an Iterable and get the same type of back out. Is this possible?
La solution
The map
method on Iterable
returns an Iterable
, so even if T
is a subclass of Iterable
, it's map
method will return Iterable
.
To get better typing, you'd have to write it like this:
import scala.collection.IterableLike
def trimNonWordCharacters[T <: Iterable[String]](items: T with IterableLike[String, T]): T =
items map { _.replaceAll("\\W", "") }
However, that won't work either, because there's no information that let a map on T
to generate another T
. For example, mapping a BitSet
into a String
cannot result in a BitSet
. So we need something else: something that teaches how to build a T
from a T
, where the mapped elements are of type String
. Like this:
import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom
def trimNonWordCharacters[T <: Iterable[String]]
(items: T with IterableLike[String, T])
(implicit cbf: CanBuildFrom[T, String, T]): T =
items map { _.replaceAll("\\W", "") }
Autres conseils
[Entering as an answer rather than a comment because code in comments doesn't format properly]
@Daniel, thanks for the explanation, I also found it useful. As Iterable derives from IterableLike, the following also seems to work, and is slightly more compact:
import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom
def trimNonWordCharacters[T <: IterableLike[String, T]]
(items: T)
(implicit cbf: CanBuildFrom[T, String, T]): T =
items map { _.replaceAll("\\W", "") }