9

Lets assume that I have a plain third party (i.e. I cannot modify it) class defined like:

class Price(var value: Int)

Is this possible to match instances of this class with some patterns?

For example, I want to implement function:

def printPrice(price: Price) = {
    // implementation here
}

... that prints price is {some value} for every price that has value <= 9000 and price is over 9000 in all other cases.

For example, calling:

printPrice(new Price(10))
printPrice(new Price(9001))

should print:

price is 10
price is over 9000

How can I implement printPrice using pattern matching?

Eugene Loy
  • 12,224
  • 8
  • 53
  • 79
  • 1
    (1) Sure `Price` has a public accessor for `value`? Because if `value` is private, there is no way you could extract that. (2) If it was say `class Price(val value: Int)`, why not just `println(s"price is ${price.value}")`? – 0__ Nov 17 '13 at 14:46

3 Answers3

17

You can create custom extractor:

package external {
    class Price(var value: Int)
}

object Price {
    def unapply(price: Price): Option[Int] = Some(price.value)
}

def printPrice(price: Price) = price match {
    case Price(v) if v <= 9000 => println(s"price is $v")
    case _ => println("price is over 9000")
}

printPrice(new Price(10))
printPrice(new Price(9001))

For case classes compiler generates it automaticaly. I think in your case extractors is overkill, but may be it's only simplified sample.

Sergey Passichenko
  • 6,920
  • 1
  • 28
  • 29
  • You are allowed to define companion objects for some class only in the same file where class is defined. Having this in mind, seems that your solution won't work for my scenario (where class comes from third party library). – Eugene Loy Nov 17 '13 at 19:48
  • 2
    Extractor is not restricted to be a companion object. You can define it anywhere. I updated my answer. – Sergey Passichenko Nov 18 '13 at 03:53
4

Thinked about accepting flavian's solution but came up with slightly better one by myself.

Here is how one could implement printPrice (without need to use wrapper objects and modifying original class):

def printPrice(price: Price) = price match {
    case p: Price if (p.value <= 9000) => println("price is " + p.value)
    case p: Price => println("price is over 9000")
}

PS: credits to flavian for showing that you can use if in pattern. Upvoting your answer for this.

Eugene Loy
  • 12,224
  • 8
  • 53
  • 79
  • 2
    Well, talking about pattern matching niceties, [you can you class-level-or as well](http://stackoverflow.com/a/15656032/298389) – om-nom-nom Nov 17 '13 at 15:36
3

You could get away with a PIMP my library pattern:

case class RichPrice(value: Int) {}
  implicit def priceToRichPrice(price: Price): RichPrice = RichPrice(price.value)

  def printPrice(x: RichPrice): Unit = {
    x match {
      case RichPrice(value) if (value <= 9000) => println("below 9000")
      case RichPrice(value) if (value > 9000) => println("over 9000")
      case _ => println("wtf")
    }
  }
println(printPrice(new Price(10)))
println(printPrice(new Price(9001)))

The point of using a case class is to let Scala define the apply method and unapply magic used for pattern matching.

flavian
  • 28,161
  • 11
  • 65
  • 105
  • 3
    Mr. downvoter is here! I think you're misusing implicit convertions with unnecessary proxying in a place where custom written unapply would be a much more cleaner solution. **EDIT** missed third-party stuff :-( – om-nom-nom Nov 17 '13 at 15:07
  • @flavian by `case def apply` and `case def unnaply` do you mean `apply` / `unapply` methods on case class (`RichPrice`)? – Eugene Loy Nov 17 '13 at 15:16