Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play JSON InvariantFunctor

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]

}
like image 605
Kevin Meredith Avatar asked Dec 19 '22 17:12

Kevin Meredith


1 Answers

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

like image 58
Travis Brown Avatar answered Mar 09 '23 08:03

Travis Brown