2

I am trying to cast a String to Int using extractors. My code looks as follows.

object Apply {
  def unapply(s: String): Option[Int] = try {
    Some(s.toInt)
  } catch {
    case _: java.lang.Exception => None
  }
}

object App {
  def toT[T](s: AnyRef): Option[T] = s match {
    case v: T => Some(v)
    case _ => None
  }
  def foo(param: String): Int = {
    //reads a Map[String,String] m at runtime
    toT[Int](m("offset")).getOrElse(0)
  }
}

I get a runtime error: java.lang.String cannot be cast to java.lang.Integer. It seems the extractor is not being used at all. What should I do?

Edit: My use case is as follows. I am using play and I want to parse the query string passed in the url. I want to take the query string value (String) and use it as an Int, Double etc. For example,

val offset = getQueryStringAs[Int]("offset").getOrElse(0)
vikraman
  • 358
  • 7
  • 14
  • Why do you expect it to be used? – pedrofurla Feb 09 '13 at 22:11
  • You tagged the question with "implicit-conversion", there is no implicit conversion going on your code. – pedrofurla Feb 09 '13 at 22:12
  • `toT[T] ... case v:T` will not work, T is lost in erasure. – pedrofurla Feb 09 '13 at 22:13
  • 3
    It is fundamentally impossible to **cast** a `String` to any other type (except for `AnyRef` or `Any`). – Randall Schulz Feb 09 '13 at 22:14
  • Oh, I did try an implicit def but that didn't work. – vikraman Feb 09 '13 at 22:17
  • @pedrofurla I have added my use case. – vikraman Feb 09 '13 at 22:26
  • 2
    If you are using play 2.1.0 (with scala 2.10) you can define "extension" methods" on `String`: `implicit class StringConversionOps(val str: String) extends AnyVal { def parseInt = try { Some(str.toInt) } catch { case _ => None } }`. You can add methods to that class as necessary. Then assuming it's in scope you can do `m("offset").parseInt.getOrElse(0)`. – huynhjl Feb 09 '13 at 23:21

2 Answers2

4

I think the biggest problem here is, that you seem to confuse casting and conversion. You have a Map[String, String] and therefore you can't cast the values to Int. You have to convert them. Luckily Scala adds the toInt method to strings through implicit conversion to StringOps.

This should work for you:

m("offset").toInt

Note that toInt will throw a java.lang.NumberFormatException if the string can not be converted to an integer.

edit:

What you want will afaik only work with typeclasses.

Here is an example:

trait StringConverter[A] {
  def convert(x: String): A
}

implicit object StringToInt extends StringConverter[Int] {
  def convert(x: String): Int = x.toInt
}

implicit object StringToDouble extends StringConverter[Double] {
  def convert(x: String): Double = x.toDouble
}

implicit def string2StringConversion(x: String) = new {
  def toT[A](implicit ev: StringConverter[A]) = ev.convert(x)
}

usage:

scala> "0.".toT[Double]
res6: Double = 0.0
drexin
  • 24,225
  • 4
  • 67
  • 81
  • That is fine, but what I want is a toT[T] method which works generically. – vikraman Feb 09 '13 at 22:37
  • Ok thanks, is this the only way this could be done? Why shouldn't a type match work? – vikraman Feb 09 '13 at 22:51
  • Because 1) your values are of type string and 2) `T` will be erased and not available at runtime (this can actually be fixed with using `Manifest`/`TypeTag`, but still you have strings, that you have to convert). – drexin Feb 09 '13 at 22:56
  • Ok, I'll use use typeclasses then, with an Option[A] return type for convert – vikraman Feb 09 '13 at 23:00
  • Why is this happening? scala> "0".toT[Int] res1: Option[Int] = Some(0) scala> def foo[T](s: String): Option[T] = s.toT[T] :10: error: could not find implicit value for parameter ev: StringConverter[T] def foo[T](s: String): Option[T] = s.toT[T] ^ – vikraman Feb 10 '13 at 00:30
  • To clarify, I can do "0".toT[Int] but, I can't create a function foo[A] that calls toT[A], and foo[Int] is called somewhere else – vikraman Feb 10 '13 at 00:31
  • 1
    Because there is no instance for `StringConverter[T]`. Try `def foo[A : StringConverter]`, this is called a type bound and adds an implicit params of type `StringConverter[A]` to your method, that is then used for the call to `toT[A]`. – drexin Feb 10 '13 at 00:36
1

There's a problem in your code, for which you should have received compiler warnings:

def toT[T](s: AnyRef): Option[T] = s match {
  case v: T => Some(v) // this doesn't work, because T is erased
  case _ => None
}

Now... where should Apply have been used? I see it declared, but I don't see it used anywhere.

EDIT

About the warning, look at the discussions around type erasure on Stack Overflow. For example, this answer I wrote on how to get around it -- though it's now deprecated with Scala 2.10.0.

To solve your problem I'd use type classes. For example:

abstract class Converter[T] {
  def convert(s: String): T
}

object Converter {
  def toConverter[T](converter: String => T): Converter[T] = new Converter[T] {
    override def convert(s: String): T = converter(s)
  }

  implicit val intConverter = toConverter(_.toInt)
  implicit val doubleConverter = toConverter(_.toDouble)
}

Then you can rewrite your method like this:

val map = Map("offset" -> "10", "price" -> "9.99")

def getQueryStringAs[T : Converter](key: String): Option[T] = {
  val converter = implicitly[Converter[T]]
  try {
    Some(converter convert map(key))
  } catch {
    case ex: Exception => None
  }
}

In use:

scala> getQueryStringAs[Int]("offset")
res1: Option[Int] = Some(10)

scala> getQueryStringAs[Double]("price")
res2: Option[Double] = Some(9.99)
Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • I did get that warning, ignored it anyway. So, how should I go about doing it? Ideally, I should be able to call toT[Int], toT[Double] etc, and they should use the appropriate unapply. – vikraman Feb 09 '13 at 22:16