Reputation: 2224
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?
Upvotes: 1
Views: 961
Reputation: 852
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.
Upvotes: 1
Reputation: 4868
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")
Upvotes: 2