简体   繁体   中英

Specialization of Scala methods to a specific tags

I have a generic map with values, some of which can be in turn lists of values. I'm trying to process a given key and convert the results to the type expected by an outside caller, like this:

// A map with some values being other collections.
val map: Map[String, Any] = Map("foo" -> 1, "bar" -> Seq('a', 'b'. 'a'))

// A generic method with a "specialization" for collections (pseudocode)
def cast[T](key: String) = map.get(key).map(_.asInstanceOf[T])
def cast[C <: Iterable[T]](key: String) = map.get(key).map(list => list.to[C].map(_.asIntanceOf[T]))

// Expected usage
cast[Int]("foo")          // Should return 1:Int
cast[Set[Char]]("bar")    // Should return Set[Char]('a', 'b')

This is to show what I would like to do, but it does not work. The compiler error complains (correctly, about 2 possible matches). I've also tried to make this a single function with some sort of pattern match on the type to no avail.

I've been reading on @specialized, TypeTag, CanBuildFrom and other scala functionality, but I failed to find a simple way to put it all together. Separate examples I've found address different pieces and some ugly workarounds, but nothing that would simply allow an external user to call cast and get an exception is the cast was invalid. Some stuff is also old, I'm using Scala 2.10.5.

This appears to work but it has a some problems.

def cast[T](m: Map[String, Any], k: String):T = m(k) match {
  case x: T => x
}

With the right input you get the correct output.

scala> cast[Int](map,"foo")
res18: Int = 1

scala> cast[Set[Char]](map,"bar")
res19: Set[Char] = Set(a, b)

But it throws if the type is wrong for the key or if the map has no such key (of course).

You can do this via implicit parameters:

val map: Map[String, Any] = Map("foo" -> 1, "bar" -> Set('a', 'b'))

abstract class Casts[B] {def cast(a: Any): B}
implicit val doubleCast = new Casts[Double] {
  override def cast(a: Any): Double = a match {
    case x: Int => x.toDouble
  }
}

implicit val intCast = new Casts[Int] {
  override def cast(a: Any): Int = a match {
    case x: Int => x
    case x: Double => x.toInt
  }
}

implicit val seqCharCast = new Casts[Seq[Char]] {
  override def cast(a: Any): Seq[Char] = a match {
    case x: Set[Char] => x.toSeq
    case x: Seq[Char] => x
  }
}


def cast[T](key: String)(implicit p:Casts[T]) = p.cast(map(key))

println(cast[Double]("foo")) // <- 1.0
println(cast[Int]("foo"))     // <- 1
println(cast[Seq[Char]]("bar")) // <- ArrayBuffer(a, b) which is Seq(a, b)

But you still need to iterate over all type-to-type options, which is reasonable as Set('a', 'b').asInstanceOf[Seq[Char]] throws, and you cannot use a universal cast, so you need to handle such cases differently.

Still it sounds like an overkill, and you may need to review your approach from global perspective

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