Sahil Sareen
Sahil Sareen

Reputation: 1834

Scala polymorphism for type casting

I have some methods like:

  def floatValueOrNone(value: String): Option[Float] = {
    if (!value.equals("NA")) Option(value.toFloat) else None
  }

  def intValueOrNone(value: String): Option[Int] = {
    if (!value.equals("NA")) Option(value.toInt) else None
  }

  def stringValueOrNone(value: String): Option[String] = {
    if (!value.equals("NA")) Option(value.toString) else None
  }

I'd like to use one method for all of these, but types can't be stored in a variable so I was wondering if there is a small clean way to do this.

Upvotes: 3

Views: 349

Answers (2)

Travis Brown
Travis Brown

Reputation: 139058

I'd recommend not using implicit conversions here. Implicit conversions make your code more difficult to reason about and maintain, slow down compilation time (this isn't often a concern, but when it is, it can be really bad), and require a special feature flag in recent Scala versions.

Instead you can use a custom type class, which I'll call Read:

trait Read[A] { def apply(s: String): Option[A] }

object Read {
  def instance[A](parse: String => A): Read[A] = new Read[A] {
    def apply(s: String): Option[A] = if (s != "NA") Option(parse(s)) else None
  }

  implicit val floatRead: Read[Float] = instance(_.toFloat)
  implicit val intRead: Read[Int] = instance(_.toInt)
  implicit val stringRead: Read[String] = instance(identity)

  def valueOrNone[A](s: String)(implicit read: Read[A]): Option[A] = read(s)
}

And then:

scala> Read.valueOrNone[Int]("100")
res0: Option[Int] = Some(100)

scala> Read.valueOrNone[String]("100")
res1: Option[String] = Some(100)

This is very similar to requiring an implicit conversion implicitly, but it has the advantage of not requiring you to pollute your implicit scope with conversions that could be applied in places you don't expect.

Upvotes: 2

slouc
slouc

Reputation: 9698

Implicit conversions to the rescue:

def valueOrNone[T](value: String)(implicit conv: String => T): Option[T] = {
  if (!value.equals("NA")) Option(conv(value)) else None
}

This will work for all cases you stated. If you want to convert to some non-standard type that doesn't have a default implicit conversion in scope, then make sure to provide one:

class MyCustomClass(str: String) { 
  override def toString = "my custom class: " + str
}

implicit def fromString(str: String) = new MyCustomClass(str)

println(valueOrNone[MyCustomClass]("foo")) // prints "my custom class: foo"

Upvotes: 1

Related Questions