简体   繁体   中英

Return a generic Traversable of a specified type

I'd like to be able to generically manipulate types like T[_] <: Traversable so that I can do things like map and filter, but I'd like to defer the decision about which Traversable I select for as long as possible.

I'd like to be able write functions against a generic T[Int] that return a T[Int] not a Traversable[Int] . So for example, I'd like to apply a function to a Set[Int] or a Vector[Int] or anything that extends Traversable and get that type back.

I first attempted to do this in a simple manner like:

trait CollectionHolder[T[_] <: Traversable[_]] {

  def easyLessThanTen(xs: T[Int]): T[Int] = {
    xs.filter(_ < 10)
  }
}

but this won't compile: Missing parameter type for expanded function. It will compile, however, if the function takes a Traversable[Int] instead of a T[Int] , so thought I could work with Traversable and convert to a T . This lead me to CanBuildFrom

object DoingThingsWithTypes {    

  trait CollectionHolder[T[_] <: Traversable[_]] {

    def lessThanTen(xs: T[Int])(implicit cbf: CanBuildFrom[Traversable[Int], Int, T[Int]]): T[Int] = {

      val filteredTraversable = xs.asInstanceOf[Traversable[Int]].filter(_ < 10)

      (cbf() ++= filteredTraversable).result
}

which compiles. But then in my tests:

val xs = Set(1, 2, 3, 4, 1000)

object withSet extends CollectionHolder[Set]

withSet.lessThanTen(xs) shouldBe Set(1, 2, 3, 4)

I get the following compiler error:

Cannot construct a collection of type Set[Int] with elements of type Int based on a collection of type Traversable[Int]. not enough arguments for method lessThanTen: (implicit cbf: scala.collection.generic.CanBuildFrom[Traversable[Int],Int,Set[Int]])Set[Int]. Unspecified value parameter cbf.

Where can I get a CanBuildFrom to make this conversion? Or better yet, how can I modify my simpler approach for the result I want? Or do I need to use a typeclass and write an implicit implementation for each Traversable I'm interested in using (one for Set, one for Vector etc)? I'd prefer to avoid the last approach if possible.

Using the (Scala 2.12.8) standard library instead of cats/scalaz/etc. you need to look at GenericTraversableTemplate . filter isn't defined there, but can easily be:

import scala.collection.GenTraversable
import scala.collection.generic.GenericTraversableTemplate

trait CollectionHolder[T[A] <: GenTraversable[A] with GenericTraversableTemplate[A, T]] {

  def lessThanTen(xs: T[Int]): T[Int] = {
    filter(xs)(_ < 10)
  }

  def filter[A](xs: T[A])(pred: A => Boolean) = {
    val builder = xs.genericBuilder[A]
    xs.foreach(x => if (pred(x)) { builder += x })
    builder.result()
  }
}

In the comment you mention nonEmpty and exists ; they are available because of the GenTraversable type bound. Really filter is too, the problem is that it returns GenTraversable[A] instead of T[A] .

Scala 2.13 reworks collections so the methods will probably be slightly different there, but I haven't looked enough at it yet.

Also: T[_] <: Traversable[_] is likely not what you want as opposed to T[A] <: Traversable[A] ; eg the first constraint is not violated if you have T[Int] <: Traversable[String] .

Yes, I am saying that you should use typeclasses .
But, you do not have to implement them, nor provide their instances for the types you need. As, those are very common and can be found in libraries like cats or scalaz .

For example, using cats :

import cats.{Traverse, TraverseFilter}
import cats.syntax.all._ // Provides the nonEmpty, filter & map extension methods to C.

import scala.language.higherKinds

def algorithm[C[_]: TraverseFilter: Traverse](col: C[Int]): C[Int] =
  if (col.nonEmpty)
    col.filter(x => x < 10)
  else
    col.map(x => x * 2) // nonsense, but just to show that you can use map too.

Which you can use like this:

import cats.instances.list._

algorithm(List(1, 200, 3, 100))
// res: List[Int] = List(1, 3)

It may be worth adding, that there are a lot of other methods like exists , foldLeft , size , etc.
Take a look to the documentation . And if it is your first time using either cats or scalaz or those concepts in general, you may find scala-with-cats very instructive.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM