In this answer I give a quick explanation of why Writes
isn't a functor—i.e., why if we have a Writes[A]
and a function A => B
we can't create a Writes[B]
in the same way that we could with Reads
.
As I note in that answer, Writes
isn't an ordinary (covariant) functor, but it is a contravariant functor, which means that if we have a Writes[A]
and a function B => A
, we can create a Writes[B]
.
Format
subsumes the functionality of both Reads
and Writes
, which means that it's neither a functor nor an contravariant functor—but it is an invariant functor (and it's really the only type with an invariant functor instance that you'll run into in the context of Play).
To see why this is so, suppose we have the following two types:
case class Foo(i: Int, s: String)
case class Bar(s: String, i: Int)
And suppose we've got a Format
instance for Foo
:
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val fooFormat = Json.format[Foo]
But that for whatever reason we can't create one in the same way for Bar
—we want to derive it from the one for Foo
. It's not enough for us to know how to create a Bar
from a Foo
, or vice versa, but if we can go both ways, we can use the invariant functor for Format
:
implicit val barFormat = fooFormat.inmap[Bar](
foo => Bar(foo.s, foo.i),
bar => Foo(bar.i, bar.s)
)
This is because we can think of Format
as a two-way pipe that allows us to put in a JsValue
and get out some A
, or to put in that A
and get out a JsValue
. If we want to convert a two-way pipe Format[A]
into a two-way pipe Format[B]
, we need adapters for both sides (i.e., both A => B
and B => A
).