2

In the Play framework source file, there is the following method inside the trait Reads[A]:

def andThen[B](rb: Reads[B])(implicit witness: A <:< JsValue): Reads[B] =
    rb.compose(this.map(witness))

and a map method like this:

def map[B](f: A => B): Reads[B] =
    Reads[B] { json => self.reads(json).map(f) }

witness is of type A <:< JsValue (which is a generalized type constraint). So how come it's being passed into the map method as an argument when the map method's parameter takes a function f: A => B??

Could someone explain? Thanks!

gourlaysama
  • 11,240
  • 3
  • 44
  • 51
platypus
  • 1,165
  • 5
  • 27
  • 47

1 Answers1

4

This is because this type witness is also a function. It is declared in Predef as:

sealed abstract class <:<[-From, +To] extends (From => To) with Serializable

So A <:< JsValue is also a function (A) => JsValue. You might wonder what the function does: it does nothing, it takes an A and directly returns it (as a JsValue).

To see why this is useful consider this example:

sealed trait Root { def bip() { println("bip") } }

def makeBip[A <: Root](a: A) {
  a.bip() // works because a is known to the type system to be a Root
}

def makeBip2[A](a: A)(implicit ev: A <:< Root) {
  a.bip() // works, because implicit resolution turns it into `ev(a).bip()`
}

The last method makeBip2 would not compile without the implicit because even though you know that a is a Root thanks to the evidence, the type system doesn't. You could cast it, it would be sure to work:

def makeBip3[A](a: A)(implicit ev: A <:< Root) {
  a.asInstanceOf[Root].bip() // ugly
}

But this doesn't feel right. If only you had a way you convert a to a Root... but wait, you do: the evidence itself!

def makeBip4[A](a: A)(implicit ev: A <:< Root) {
  ev(a).bip() // works!
}

And since implicit parameters are available as implicits within the method, a.bip() will be automatically converted to ev(a).bip() and you never need to know a function was involved.

However, the type system only uses the implicit to resolve an A into a JsValue, but not a Seq[A] into a Seq[JsValue] or a Reads[A] into a Reads[JsValue].

So in you case, this.map(witness) just makes the type system understand that a Reads[A] is a Reads[JsValue] by applying that function that does nothing, so that it can be composed with something that takes a JsValue and returns a B.

See the Generalized type constraits question on SO for more.

Community
  • 1
  • 1
gourlaysama
  • 11,240
  • 3
  • 44
  • 51