0

I want to use a Scala extractor to match my custom type with a String (in that specific order, not String to Scala). Here is my code:

class Scala

object Scala {
  def apply(s: String) = new Scala
  def unapply(sc: Scala) = Some("Scala")
}

class ExtendedString(s: String) {
  def unapply(sc: Scala): Option[String] = {
    Some(s)
  }
}

implicit def str2Scala(s: String): ExtendedString = {
  new ExtendedString(s)
}

Scala match {
  case "abc" => println("Aha!")
  case "def" => println("Yeah!")
  case "scala" => println("Scala!")
}

But it does not work. I get an error:

Error:(20, 9) type mismatch;
 found   : String("abc")
 required: A$A109.this.Scala.type
  case "abc" => println("Aha!")
       ^

How can I fix it to make extractor work?

PS

My original idea was to provide an implicit converter to extend String class with ExtendedString class and then to implement unapply method there to make extraction possible.

Finkelson
  • 2,921
  • 4
  • 31
  • 49
  • Possible duplicate of [enrich PartialFunction with unapply functionality](http://stackoverflow.com/questions/33182235/enrich-partialfunction-with-unapply-functionality) – Archeg Oct 19 '15 at 23:27
  • No, I don't think they are the same questions. I'm trying to match a custom type to string. A String class does not have `unapply` function to get my custom type. – Finkelson Oct 20 '15 at 12:27
  • You've tried to use implicit conversions in match which is not something scala works like. Scala match expression is not a scala generic expression, and implicits don't work there. It is explained in that question. On how to do that properly, see @Dylan answer. – Archeg Oct 20 '15 at 12:42

1 Answers1

2

There should be no need whatsoever for implicits if all you want is an extractor.

To define an extractor, you make an object with an unapply method. E.g.

object MyExtractor {
  def unapply(value: ValueBeingMatched): Option[ExtractedValue] = { ... }
}

Then you match a value using the extractor

val myValue: ValueBeingMatched = ...
myValue match {
  case MyExtractor(extractedValue) => println(s"I got $extractedValue!")
}

If you want your extractor to come back with multiple values, the unapply should return an option of tuple:

object MyExtractor {
  def unapply(value: ValueBeingMatched): Option[(ResultType1, ResultType2, ...)] = { ... }
}

val myValue: ValueBeingMatched = ...
myValue match {
  case MyExtractor(result1, result2, ...) => ...
}

I'm not clear on what you are trying to accomplish from your example code, so I'll make an example that maybe is relevant for you.

Let's say you make a custom Point class:

case class Point(x: Int, y: Int)

And you want to be able to extract points from a String. In this case, the ValueBeingMatched is a String, and the ExtractedValue is a Point. I'll also define the extractor in the Point object. As for functionality, let's assume that a string like "12,8" corresponds to a Point(12, 8).

object Point {
  def unapply(s: String): Option[Point] = {
    val parts = s.split(",")
    // this is just example code, so I won't handle failures,
    // but if it did fail, you'd return a None instead of Some
    Some(Point(parts(0).toInt, parts(1).toInt))
  }
}

Now that it's defined, you can match strings using the Point extractor:

val s = "3,4"
s match {
  case Point(p) => // p is actually an instance of Point
}

edit to respond to feedback:

In order to match directly to a string, the value being matched must already be a String. So one way would be to add a converter method e.g.

instanceOfMyType.convertToString match {
  case "abc" => println("Aha!")
}

Or you would have to write an extractor to allow

instanceOfMyType match {
  case Extracted("abc") => println("Aha!")
}
Dylan
  • 13,645
  • 3
  • 40
  • 67