Play JSON InvariantFunctor

functor, playframework, scala

Solution

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`).

Problem

Play's JSON library includes a `Functor` and `Invariant Functor`: I've seen `Functor` before: ``` trait Functor[M[_]] extends Variant[M] { def fmap[A, B](m: M[A], f: A => B): M[B] } ``` But, conceptually, why is it necessary to provide both functions `f1` and `f2` for `InvariantFunctor`? ``` trait InvariantFunctor[M[_]] extends Variant[M] { def inmap[A, B](m: M[A], f1: A => B, f2: B => A): M[B] } ```

Original source