qed
qed

Reputation: 23104

How does the extractor work when the `unapply` function returns Boolean instead of Option?

I learned about extractors from the stairway book:

    object Twice {
      def apply(x: Int) = x * 2
      def unapply(x: Int) = if(x % 2 == 0) Some(x / 2) else None
    }
    // outside pattern mathcing, Twice.apply(21) is called
    val x = Twice(21)
    x match {
        // inside pattern matching, Twice.unapply(x) is called,
        // the result Some(21) is matched against y,
        // y gets the value 21
      case Twice(y) => println(x + " is twice " + y)
      case _ => println(x + " is odd.")
    }

That's pretty straight forward. But today I read from some book on Play framework this code:

trait RequestExtractors extends AcceptExtractors {

  //Convenient extractor allowing to apply two extractors. 
  object & { 
    def unapply(request: RequestHeader): Option[(RequestHeader, RequestHeader)] = Some((request, request)) 
  } 

}

//Define a set of extractors allowing to pattern match on the Accept HTTP header of a request 
trait AcceptExtractors {

  //Common extractors to check if a request accepts JSON, Html, etc. 
  object Accepts { 
    import play.api.http.MimeTypes 
    val Json = Accepting(MimeTypes.JSON) 
    val Html = Accepting(MimeTypes.HTML) 
    val Xml = Accepting(MimeTypes.XML) 
    val JavaScript = Accepting(MimeTypes.JAVASCRIPT) 
  } 

}

//Convenient class to generate extractors checking if a given mime type matches the Accept header of a request. 
case class Accepting(val mimeType: String) {
  def unapply(request: RequestHeader): Boolean = request.accepts(mimeType) 
  def unapply(mediaRange: play.api.http.MediaRange): Boolean = mediaRange.accepts(mimeType) 
}


def fooBar = Action { 
    implicit request => 
      val xmlResponse: Node = <metadata> 
        <company>TinySensors</company> 
        <batch>md2907</batch> 
      </metadata> 

      val jsonResponse = Json.obj("metadata" -> Json.arr( 
        Json.obj("company" -> "TinySensors"), 
        Json.obj("batch" -> "md2907")) 
      ) 

      render { 
        case Accepts.Xml() => Ok(xmlResponse) 
        case Accepts.Json() & Accepts.JavaScript() => Ok(jsonResponse) 
      }
  }

How does the extractor work when the unapply function returns Boolean instead of Option? How do &, Accepts.Xml work here?

Upvotes: 0

Views: 233

Answers (2)

qed
qed

Reputation: 23104

Ok, I found a way to figure this out by making a minimal example:

object Unapply {

  case class DividedBy(val number: Int) {
    def unapply(divider: Int): Boolean = number % divider == 0
    def unapply(divider: Double): Boolean = number % divider.toInt == 0
  }

  val x = DividedBy(15)
  // y should be true
  val y = 5 match {
  //    case DividedBy(15)() => true
    case x() => true
    case _ => false
  }
}

The weird thing is that when you use DividedBy(15)() (commented out above), the code won't compile.


Update:

object Unapply {
  case class Division(val number: Int) {
//    def unapply(divider: Int): Boolean = number % divider == 0
    def unapply(divider: Int): Option[(Int, Int)] = if (number % divider == 0) Some(number/divider, 0) else None
    def unapply(divider: Double): Boolean = number % divider.toInt == 0
  }

  object Division {
    def apply(number: Int) = new Division(number)
  }

  val divisionOf15 = Division(15)
  // y should be true
  val y = 5 match {
  //    case DividedBy(15)() => true
    case divisionOf15(z, w) => s"$z, $w"
    case _ => s"Not divisible"
  }

  val z = 5.0 match {
    case divisionOf15() => "Divisible"
    case _ => "Not divisible"
  }
}

After some reading some old notes on the stairway book now I have a clearer understanding of this. The case class is a extractor factory.

Upvotes: 1

PhilBa
PhilBa

Reputation: 732

I can really tell you about the play framework, but if used in pattern matching an extractor returning a boolean signifies if the pattern matches. Thus if an extractor return true it means that the pattern matches the value. This is a good link about extractors and also covers this case: http://danielwestheide.com/blog/2012/11/21/the-neophytes-guide-to-scala-part-1-extractors.html

Generally you use extractors for two use cases:

1) Destructing an object, which means returning one or more values which represent the state of given object

2) You can also use extractors to turn an object into an object of another kind during pattern matching. I made a small example for this case:

class Division(val number: Int) {
}

object Division {
    def unapply(divider: Division): Boolean = divider.number != 0

    def unapply(divider: Int): Option[Division] = if (divider != 0) Some(new Division(divider)) else None
}

val divident = 15
val divider = 5
val y = divider match {
    case Division(notZero) => divident / notZero.number //notZero is of type Division
    case _ => throw new IllegalArgumentException()
}

Upvotes: 1

Related Questions