vkubicki
vkubicki

Reputation: 1124

How to avoid type argument erasure

My code uses Scala and the Breeze linear algebra library. I have objects of types DenseVector[Double], DenseVector[Int], etc... where DenseVector is an array-like container with dedicated methods for numerical computations. I sometimes need to use pattern matching against the contained type. Type-erasure forced me to introduce a trait and "wrapping" case classes:

sealed trait DenseVectorRoot
case class DenseVectorReal(val data: DenseVector[Real]) extends DenseVectorRoot
case class DenseVectorMatrixReal(val data: DenseVector[DenseMatrix[Real]]) extends DenseVectorRoot

(Where Real is just an alias for Double).

Pattern matching would look like:

def print(data: DenseVectorRoot) =
  data match {
    case DenseVectorMatrixReal(_) => println("Contains real matrices")
    case DenseVectorReal(_) => println("Contains real scalars")
  }

I would like to get rid of the DenseVectorRoot trait. I attempted this:

def print2(data: DenseVector[_ <: Any]) =
  data match {
    case _: DenseVector[Double] => println("Contains real matrices")
    case _: DenseVector[Int] => println("Contains real scalars")
  }

But the type arguments get erased.

How should I modify print2 using ClassTags so that pattern matching works ? For example by printing the correct output in the following code:

val v0 = DenseVector(1.2, 1.5, 1.6)
val v1 = DenseVector(3, 4, 5)

val a = Array(v0, v1)
a.map(print2)

EDIT

The main reason why I need to manage an Array with varying containers is that my code need to manage various types of data (for example, parsing input would be different for a DenseVector[Real] and for a DenseVector[Matrix[Real]]). My current design is to store everything in an Array[DenseVectorRoot], and then process the data using high-order functions like .map(). Each of this function will, on an element-to-element basis, pattern-match to know if the data is a DenseVectorReal or a DenseVectorMatrixReal, and act accordingly.

That might not be the optimal design to solve my problem, but I do not know at compile time what types of data are provided by a user. I would be happy to know any better design !

Upvotes: 1

Views: 259

Answers (2)

ziggystar
ziggystar

Reputation: 28676

Use TypeTag

You can request that the compiler infers the parameter type, and generates a TypeTag for you. You can then use the TypeTag to check for a certain type, or you can print it for debugging purposes.

Example Code

import scala.reflect.runtime.universe._

def printType[A: TypeTag](a: List[A]): Unit = 
  println(if(typeTag[A] == typeTag[Double]) "Double" else "other")

printType(List(1.0))
printType(List(1))

Output

>Double
>other

Upvotes: 1

Dima
Dima

Reputation: 40508

This type of thing is better done with type classes:

  trait DenseContent[T] {
    def compute(v: DenseVector[T]): String
  }
  object DenseContent {
    implicit object _Real extends DenseContent[Real] {
      def compute(v: DenseVector[Real]) = "real"
    }
    implicit object _Int extends DenseContent[Int] {
      def compute(v: DenseVector[Int]) = "int"
    }
    // etc ...
  }

  def print2[T : DenseContent](data: DenseVector[T]) = println(
     implicitly[DenseContent[T]].compute(data)
  )

Upvotes: 1

Related Questions