0
votes

I have been searching around to achieve this, even with Manifest and Reflect API, it's still hard to achieve.

With Manifest and Reflection, I can match List[Any] to a class(List[A]), I am also able to get match by type T, just as in http://daily-scala.blogspot.co.uk/2010/01/overcoming-type-erasure-in-matching-1.html

How save a TypeTag and then use it later to reattach the type to an Any (Scala 2.10)

but how can I make sure the type of the input and use it in a method?

Say,

object test {

val list : List[List[Any]] = List(
  List(2.5, 3.6 ,7.9),
  List("EUR","HKD", "USD")
)

def calculateString(in:List[String]) = {
  println("It's a String List")
  println(in)
}

  def calculateDouble(in:List[String]) = {
    println("It's a Double List")
    println(in)
  }

  def main( args: Array[String]){
    list.foreach(l=> matchAndCalculate(l))
  }


  // Copy from Andrzej Jozwik it doesn't work, but it's good to demonstrate the idea
  def matchAndCalculate(list:List[Any]) = list match {
    case i if i.headOption.exists(_.isInstanceOf[Long]) =>  calculateLong(i)
    case i if i.headOption.exists(_.isInstanceOf[String]) =>  calculateString(i)
  }

}

Many Thanks

Harvey

PS: As Sarah pointed out that it might be the only way that keeping type manifest while I create the list in the first before I put them into more complex structure.

Here's the challenge: is that possible to cast List[Any] back to / match to something say List[String] and as input to method like def dummyMethod(stringList: List[String]) without pissing off compiler?

2
In your code change line list.foreach(l=> matchAndCalculate(list)) to list.foreach(l=> matchAndCalculate(l)) – Andrzej Jozwik

2 Answers

1
votes

Unless you can change your data structure, Andrej's solution is the only reasonable way to do this.

You can't really use type manifests, because you have two levels of indirection. You'd need a different type manifest for every member of the outer list. E.g., you could have a List[(List[Any], TypeTag[Any])], but there's no way to get compile-time information about every individual row out of a List unless you build that information at the time that you're constructing the lists.

If you really wanted to carry along static type information, it would be easy to do this with implicits and just make each entry in your outer list a special class.

One simple variant might look like this:

class CalculableList[A](val elements: List[A])(implicit val calc: Calculator[A]) {
  def calculate = calc(elements)
}
trait Calculator[-A] extends (List[A] => Unit)
implicit object StringCalc extends Calculator[String] {
  def apply(in: List[String]) {
    println("It's a String List")
    println(in)
  }
}
implicit object DoubleCalc extends Calculator[Double] {
  def apply(in: List[Double]) {
    println("It's a Double List")
    println(in)
  }
}

val list: List[CalculableList[_]] = List(
  new CalculableList(List(1.0, 2.0, 3.0)),
  new CalculableList(List("a", "b", "c"))
)

list foreach { _.calculate }

Another option for this kind of generic programming is to use Miles Sabin's Shapeless. This uses special data structures to let you construct arbitrary-sized tuples that can be treated like type-safe lists. It generates a data structure similar to a linked list with a generic type wrapper that keeps track of the type of each row, so you wouldn't want to use it unless your lists are fairly short. It's also a bit difficult to learn, maintain and understand—but it opens up some deep wizardry when you understand and use it appropriately.

I don't know enough about your use case to know whether Shapeless is advisable in this case.

In Shapeless for Scala 2.11, a solution would look something like this:

import shapeless._
val lists = List(1.0, 2.0, 3.0) ::
            List("a", "b", "c") ::
            HNil

object calc extends Poly1 {
  implicit def doubleList = at[List[Double]] { in =>
    println("It's a double list")
    println(in)
  }
  implicit def stringList = at[List[String]] { in =>
    println("It's a string list")
    println(in)
  }
}

lists map calc
1
votes
    def calculateString(in:List[String]) = {
      println("It's a String List")
      println(in)
    }

    def calculateDouble(in:List[Double]){
     println("It's a Double List")
        println(in)
    }

    def castTo[T](t:T,list:List[Any]) = list.asInstanceOf[List[T]] 

   def matchAndCalculate(list:List[Any]) = list.headOption match {
        case Some(x:Double) => calculateDouble(castTo(x,list))
        case Some(x:String) => calculateString(castTo(x,list))         
        }

And check:

scala> matchAndCalculate(List(3.4))
It's a Double List
List(3.4)

scala> matchAndCalculate(List("3.4"))
It's a String List
List(3.4)

scala> val list : List[List[Any]] = List(
     |   List(2.5, 3.6 ,7.9),
     |   List("EUR","HKD", "USD")
     | )
list: List[List[Any]] = List(List(2.5, 3.6, 7.9), List(EUR, HKD, USD))

scala> list.foreach(l=> matchAndCalculate(l))
It's a Double List
List(2.5, 3.6, 7.9)
It's a String List
List(EUR, HKD, USD)