why do i need to add the type annotation at the first line?
c.get[List[String]]("primary-group")
is Decoder.Result[List[String]]
after flatMap
it should keep the top type and be Decoder.Result[String]
but it changes to Either[DecodingFailure, String]. Why? Is the problem that it is dependent type
?
case class JWTPayload(primaryGroup: Group, groupMember: List[Group], name: String, pid: String)
implicit val jwtPayloadDecoder: Decoder[JWTPayload] = Decoder.instance(c =>
(
c.get[List[String]]("primary-group").flatMap(l => if(l.size == 1) l.head.asRight else DecodingFailure("", c.history).asLeft) : Decoder.Result[String],
c.get[List[String]]("group-member"),
c.get[String]("name"),
c.get[String]("pid")
).map4(
JWTPayload
)
)
Without : Decoder.Result[String
I get
Error:(43, 7) value map4 is not a member of (scala.util.Either[io.circe.DecodingFailure,String], io.circe.Decoder.Result[List[String]], io.circe.Decoder.Result[String], io.circe.Decoder.Result[String])
possible cause: maybe a semicolon is missing before `value map4'?
).map4(
Thanks
This is not a full answer but I hope it will provide some insights. The crucial part here is how map4
is implemented. As of cats 0.9 it is done via cats.syntax.TupleCartesianSyntax
trait and its implicit catsSyntaxTuple4Cartesian
which wraps a 4-tuple into a cats.syntax.Tuple4CartesianOps
class (in cats 1.0 "cartesian" was changed to "semigroupal"). This code is auto-generated for all tuples up to 22 by Boilerplate.scala. The auto-generated code looks something like this:
implicit def catsSyntaxTuple4Cartesian[F[_], A0, A1, A2, A3](t4: Tuple4[F[A0], F[A1], F[A2], F[A3]]): Tuple4CartesianOps[F, A0, A1, A2, A3] = new Tuple4CartesianOps(t4)
private[syntax] final class Tuple4CartesianOps[F[_], A0, A1, A2, A3](t4: Tuple4[F[A0], F[A1], F[A2], F[A3]]) {
def map4[Z](f: (A0, A1, A2, A3) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map4(t4._1, t4._2, t4._3, t4._4)(f)
def contramap4[Z](f: Z => (A0, A1, A2, A3))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap4(t4._1, t4._2, t4._3, t4._4)(f)
def imap4[Z](f: (A0, A1, A2, A3) => Z)(g: Z => (A0, A1, A2, A3))(implicit invariant: Invariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.imap4(t4._1, t4._2, t4._3, t4._4)(f)(g)
def apWith[Z](f: F[(A0, A1, A2, A3) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap4(f)(t4._1, t4._2, t4._3, t4._4)
}
Note the F[_]
(functor) type parameter. Effectively this code adds map4
method to any 4-tuple where each inner type is the same functor over some types.
So assuming you did import cats.implicits._
, after (partial) implicits resolution your code is actually something like this:
cats.implicits.catsSyntaxTuple4Cartesian[Decoder.Result, String, List[String], String, String](
c.get[List[String]]("primary-group").flatMap(l => if (l.size == 1) l.head.asRight else DecodingFailure("", c.history).asLeft): Decoder.Result[String],
c.get[List[String]]("group-member"),
c.get[String]("name"),
c.get[String]("pid")
).map4[JWTPayload](
JWTPayload
)
When you don't specify Decoder.Result[String]
, Scala compiler is not smart enough to get that it should split Either[DecodingFailure, String]
into a functor type Either[DecodingFailure, _]
and String
and then
there will be matching Functor
and Cartesian
implicit objects (actually provided by the cats.implicits
object via cats.instances.AllInstances
and cats.instances.EitherInstances
traits)
it would match the functor type used for other 3 fields in the tuple (i.e. Decoder.Result[_]
).
So I think this behavior is a result of a combination of the fact that map4
is added to 4-tuple via an implicit Ops-class and the fact that the underlying type is Either
which is 2-places generic type rather than a simple Functor
.