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?

Was it helpful?

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", "") }

OTHER TIPS

[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", "") }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top