1

I want to read in a CSV file with data and an accompanying CSV file with the types of each column in the first file and cast the data types appropriately. Example:

data file:

a,b,c
1,two,3.333

types file:

column, type
a, int
b, string
c, double

My attempt so far has been to write a function that determines the correct conversion function from a String to the appropriate type. The problem is, the "appropriate type" can be multiple different possible types, depending on the input.

def converter[T](columnType: String): String => T = {
    columnType match {
        case "int" => (s: String) => s.toInt
        case "double" => (s: String) => s.toDouble
        case "string" => (s: String) => s
    }
 }

This does not compile because the type T cannot be inferred.

I looked into the possibility of using the Either keyword but the syntax becomes cumbersome with more than 2 possible return types.

Is there a nice way to do this?

2
  • 1
    I think there's no way for the scala compiler to statically infer the type of T, as it's determined at runtime. In other terms, T depends on the runtime value of columnType Commented Jul 19, 2016 at 17:37
  • Correct. What if I impose the constraint that T will be one of multiple types, e.g. Int,Double,String? I can do that sloppily using Either[Int,Either[Double,String]] but that gets ugly very quickly... Commented Jul 19, 2016 at 19:02

2 Answers 2

2

A very unethical solution would be to simply cast the instance to "assist" the compiler

def converter[T](columnType: String): String => T = {
  columnType match {
    case "int"  => (s: String) => s.toInt.asInstanceOf[T]
    case "double" => (s: String) => s.toDouble.asInstanceOf[T]
    case "string" => (s: String) => s.asInstanceOf[T]
  }
}

val stringConverter = converter[String]("string")
val intConverter = converter[Int]("int")
val doubleConverter = converter[Double](doubleConverter)

stringConverter("hello")
intConverter("1")
doubleConverter("3.00")

A cleaner yet still unorthodox solution would be to use the type class to determine the type.

def converter[T : Manifest]: String => T = {
  implicitly[Manifest[T]].runtimeClass match {
    case x if classOf[Int] == x => (s: String) => s.toInt.asInstanceOf[T]
    case x if classOf[Double] == x => (s: String) => s.toDouble.asInstanceOf[T]
    case x if classOf[String] == x => (s: String) => s.asInstanceOf[T]
  }
}

val stringConverter = converter[String]
val intConverter = converter[Int]
val doubleConverter = converter[Double]

stringConverter("hello")
intConverter("1")
doubleConverter("3.00") 

What you probably want to look into is typeclasses: https://youtu.be/sVMES4RZF-8

Here is an example of what a typeclass solution would look like:

trait Reader[T] {
  def convert(s: String): T
}

implicit val doubleReader = new Reader[Double] {
  override def convert(s: String) = s.toDouble
}

implicit val intReader = new Reader[Int] {
  override def convert(s: String) = s.toInt
}

implicit val stringReader = new Reader[String] {
  override def convert(s: String) = s
}

def converter[T: Reader]: String => T = implicitly[Reader[T]].convert

val stringConverter = converter[String]
val intConverter = converter[Int]
val doubleConverter = converter[Double]

stringConverter("hello")
intConverter("1")
doubleConverter("3.00")
Sign up to request clarification or add additional context in comments.

4 Comments

Interesting, I hadn't explored typeclasses before. It's still not exactly what I was hoping for, since converter still needs a type parameter. I was hoping I could do something like converter[T <: Any]: String => T but that doesn't work either...
There are many benefits to a typeclass. 1. You can add more implicit "converts" when the need arises. - It adheres to the Open-Close principle. 2. Compile safe, if no implicit "Reader" is present for the requested type it will not compile. 3. Can help decouple multiple layers of your system (not present in this example).
@spiffman if you had convert[T <: Any] you would be basically bypassing the type system and run into RuntimeExceptions if by "accident" you try to convert something you don't have a conversion for.
True, I'm actually ok with runtime errors in that case, but even that solution requires a type parameter to convert, so nothing gained...
1

Another alternative to the excellent answer above is this, if you'd like just one conversion method to call:

// Define typeclass (plus companion object for convenience)

trait FromStringConverter[T] {
  def convert(string: String): T
}

object FromStringConverter {
  def apply[T](f: String => T): FromStringConverter[T] = new FromStringConverter[T] {
    override def convert(string: String): T = f(string)
  }
}

// Define converters

implicit val intConverter = FromStringConverter[Int] { string =>
  string.toInt
}

implicit val doubleConverter = FromStringConverter[Double] { string =>
  string.toDouble
}

implicit val identityConverter = FromStringConverter[String] { string =>
  string
}

// Define conversion method

def convertStringTo[T : FromStringConverter](string: String): T =
  implicitly[FromStringConverter[T]].convert(string)

// OR alternately (this has the same effect as 'convertStringTo')

def convertStringTo2[T](string: String)(implicit ev: FromStringConverter[T]): T =
  ev.convert(string)

// Test

convertStringTo[Int]("3")         // => 3
convertStringTo[Double]("3.0")    // => 3.0
convertStringTo[String]("three")  // => "three"

convertStringTo2[Int]("3")        // => 3
convertStringTo2[Double]("3.0")   // => 3.0
convertStringTo2[String]("three") // => "three"

You can easily add more implicit converters to support any type, so long as you can provide the logic for converting a string to that type.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.