8
votes

I want to be able to do this:

scala> val Int(i) = "1"
i: Int = 1

But Int doesn't have an unapply method.

I found this answer which gives instructions on how to implicitly add a method to an existing object, so I tried it out. The solution they gave works, but unfortunately not for pattern matching. Here's what I have:

object UnapplyInt {
  val IntRE = """^(\d+)$""".r
  def unapply(v: String): Option[Int] = v match {
    case IntRE(s) => Some(s.toInt)
    case _ => None
  }
}
implicit def int2unapplyInt(objA: Int.type) = UnapplyInt

These test cases are all fine:

val UnapplyInt(i) = "1"       // pattern matching with unapply is fine
val i = Int.unapply("1").get  // implicit conversion is fine

But the one I want fails:

scala> val Int(i) = "1"
<console>:10: error: object Int is not a case class constructor, nor does it have an unapply/unapplySeq method
       val Int(i) = "1"
           ^

If the implicit conversion works and pattern matching with unapply works, why doesn't Scala put these two things together for implicit pattern matching?

1

1 Answers

8
votes

edit So my original reasoning was no good. The real reason is from Section 8.1.8 of the Scala language spec

Syntax:
    SimplePattern ::= StableId ‘(’ [Patterns] ‘)’

That is, the extractor object has to be stable, and an implicit conversion is not stable. No explanation is given for why the extractor must be stable; I suspect it is because Scala does not want to treat the extractor as an expression because that could quickly become ambiguous:

... match {
    foo(bar)(baz)
}

Now which is the constructor and which are the pattern variables?

Luckily you can do this and it works just fine (though, as you commented, introduces other problems):

object Int {
    def unapply(v: String) = try Some(v.toInt)
        catch { case _: NumberFormatException => None }
}

val Int(i) = "5"

since the type Int and the object Int are in different namespaces.