0

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

user1698641
  • 211
  • 1
  • 12

1 Answers1

2

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

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

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

SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Strange behaviour me is that if I have type Result[A] = Either[DecodingFailure, A] val a: Result[String] = Right("123") // when I do not write type it is Either[DecodingFailure, Int] val defaultTypeInference = a.map(Integer.parseInt) // coerced to be Result[Int] val coercedToBeResult: Result[Int] = a.map(Integer.parseInt) Why map of flatMap in this case do not preserve the Result type? I know this is not type just a type alias but anyway – user1698641 Nov 24 '17 at 22:43
  • @user1698641, I'm not sure I get your point. If I change your third line to something that uses `map` like `c.get[String]("name").map(x => x + x)`, it also stop to compile unless I provide an explicit type hint `Decoder.Result[String]`. The issue in this scenario is not that the type is lost when you do `flatMap`. Types `Result[A]` and `Either[DecodingFailure, A]` are really the same and the Scala compiler knows it. The issue is that this non-trivial **_split_** of the `Either[_,_]` type into `Decoder.Result[_]` is lost and the Scala compiler is not smart enough to make it again on its own. – SergGr Nov 25 '17 at 00:37