0

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?

qed
  • 22,298
  • 21
  • 125
  • 196

2 Answers2

1

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.

qed
  • 22,298
  • 21
  • 125
  • 196
  • Extractors need to be defined in a companion object, not the class itself. – PhilBa Jan 10 '16 at 21:21
  • Going back to your Play example, it seem to be used to specify which MIMEs you want to accept, and how you want to handle them. – PhilBa Jan 10 '16 at 21:28
  • I rewrote your example a bit and posted it on github: https://gist.github.com/Phil-Ba/7725cba259973834ae70#file-extractor-scala – PhilBa Jan 10 '16 at 22:01
  • @PhilBa Interesting, it seems the source code of Play defined extractors in the class itself. What would be the harm of doing so? – qed Jan 10 '16 at 22:30
  • Another thing about your example. Scala automatically creates some extractors for case classes. I dont know about that, but maybe you could post a link to the source code? If Play is using CaseClasses, it might that extractors for case classes are defined differently,but I didnt find any information on this. – PhilBa Jan 10 '16 at 22:35
  • Hmm, ok this seems interesting. Accepts.Xml() seems to call the unapply method of an existing Accepting instance. I'll try to look up if i can find some info on this – PhilBa Jan 10 '16 at 22:52
  • Ok, I found this in the SLS: "An unapply method in an object x matches the pattern x(p1,…,pn) if it takes exactly one argument and one of the following applies: n=0 and unapply's result type is Boolean. In this case the extractor pattern matches all values v for which x.unapply(v) yields true." Since this is the only case where the type isnt mentioned in the SLS, I guess that in this special case you can match with the unapply method of an instance of a type. – PhilBa Jan 10 '16 at 22:56
  • 1
    @PhilBa see docs for scala.util.matching.Regex for examples of pattern matches where extractor is just some instance. SLS 8.1.8 says StableID is required, which is why you can't create the extractor object in the case clause, like `case createX(15)() =>`. – som-snytt Jan 11 '16 at 00:59
1

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()
}
PhilBa
  • 732
  • 4
  • 16