0

I've stumbled upon this thread and there @prolativ provided some fancy scala 3 syntax for creating extension methods for objects

extension (int: Int.type)
  def unapply(s: String): Option[Int] = s.toIntOption

But this got me thinking if there is a way to do something similar in scala 2?

I've tried

implicit class IntOps(s: Int.type) {
  def unapply(s: String): Option[Int] = s.toIntOption
}

Which seemed just scala 2 translation, but it wont compile with error object Int is not a case class, nor does it have a valid unapply/unapplySeq member.

UPD In case of pattern matching there is a way to do this - just add new object and match it:

object IntOps {
  def unapply(s: String): Option[Int] = s.toIntOption
}

but the question remains - how to add extension methods to object?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Killjoyer
  • 5
  • 2

1 Answers1

2

An implicit class is a correct way to add extension method to an object

implicit class IntOps(obj: Int.type) {
  def parse(s: String): Option[Int] = s.toIntOption
}

Int.parse("123") // Some(123)

And unapply can be added in the same way

implicit class IntOps(obj: Int.type) {
  def unapply(s: String): Option[Int] = s.toIntOption
}

Int.unapply("123") // Some(123)

Just this will have no impact on pattern matching

"123" match {
  case Int(i) => ??? // doesn't compile: object Int is not a case class, nor does it have a valid unapply/unapplySeq member
}

The place where Scala 2 compiler throws object Int is not a case class, nor does it have a valid unapply/unapplySeq member is here: https://github.com/scala/scala/blob/v2.13.10/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala#L121-L122

else if (!reallyExists(member))
  CaseClassConstructorError(fun, s"${fun.symbol} is not a case class, nor does it have a valid unapply/unapplySeq member")

This is based on symbols (kind of typeOf[Int.type].decl(TermName("unapply")) in scala-reflect terms). Implicit conversions (extension methods) are not checked here.

This is modified in Scala 3: https://github.com/lampepfl/dotty/blob/3.2.2/compiler/src/dotty/tools/dotc/typer/Applications.scala#L1291-L1341 The compiler tries to typecheck x.unapply and this includes implicit conversions.

By the way, when Scala 2 compiler decides whether to generate unapply in the companion object of a case class (Int wasn't a case class) the compiler doesn't check extension methods either

How can I view the code that Scala uses to automatically generate the apply function for case classes?

case class MyClass(i: Int)

MyClass(42) match {
  case MyClass(i) => println(i) // 42
}
case class MyClass(i: Int)
object MyClass {
  def unapply(mc: MyClass): Option[Int] = Some(100)
}

MyClass(42) match {
  case MyClass(i) => println(i) // 100
}
case class MyClass(i: Int)

implicit class MyClassCompanionOps(mc: MyClass.type) {
  def unapply(mc: MyClass): Option[Int] = Some(100)
}

MyClass(42) match {
  case MyClass(i) => println(i) // 42
}

And this behavior is the same in Scala 3.


How to extend String to add new unapply function for using it in extracting?

enrich PartialFunction with unapply functionality

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66