【问题标题】:Type resolution with flatMap使用 flatMap 进行类型分辨率
【发布时间】:2018-05-07 15:30:06
【问题描述】:

为什么我需要在第一行添加类型注释? c.get[List[String]]("primary-group")flatMap 之后是Decoder.Result[List[String]],它应该保持顶部类型并且是Decoder.Result[String],但它会更改为Either[DecodingFailure, String]。为什么?是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
    )
  )

没有: Decoder.Result[String我得到

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(

谢谢

【问题讨论】:

    标签: scala scala-cats circe


    【解决方案1】:

    这不是一个完整的答案,但我希望它能提供一些见解。这里的关键部分是如何实现map4。从猫 0.9 开始,它是通过 cats.syntax.TupleCartesianSyntax 特征及其隐含的 catsSyntaxTuple4Cartesian 完成的,它将 4 元组包装到 cats.syntax.Tuple4CartesianOps 类中(在猫 1.0 中,“笛卡尔”更改为“半群”)。此代码由Boilerplate.scala 为最多 22 个的所有元组自动生成。自动生成的代码如下所示:

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

    注意F[_](函子)类型参数。实际上,此代码将 map4 方法添加到任何 4 元组,其中每个内部类型在某些类型上是相同的函子。

    所以假设你做了import cats.implicits._,在(部分)隐式解析之后,你的代码实际上是这样的:

    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
    )
    

    当您不指定Decoder.Result[String] 时,Scala 编译器不够聪明,无法将Either[DecodingFailure, String] 拆分为函子类型Either[DecodingFailure, _]String,然后

    1. 会有匹配的FunctorCartesian隐式对象(实际上由cats.implicits对象通过cats.instances.AllInstancescats.instances.EitherInstances特征提供)

    2. 它将匹配用于元组中其他 3 个字段的函子类型(即Decoder.Result[_])。

    所以我认为这种行为是通过隐式 Ops 类将 map4 添加到 4 元组的事实以及基础类型是 Either 的事实相结合的结果,这是 2 位通用输入而不是简单的Functor

    【讨论】:

    • 我的奇怪行为是,如果我有类型 Result[A] = Either[DecodingFailure, A] val a: Result[String] = Right("123") // 当我不写类型是Either[DecodingFailure, Int] val defaultTypeInference = a.map(Integer.parseInt) // 强制为Result[Int] val coercedToBeResult: Result[Int] = a.map(Integer.parseInt) 为什么map的flatMap在这个case 不保留 Result 类型?我知道这不仅仅是类型别名,而是无论如何
    • @user1698641,我不确定我明白你的意思。如果我将您的第三行更改为使用map 之类的c.get[String]("name").map(x => x + x),它也会停止编译,除非我提供明确的类型提示Decoder.Result[String]。这种情况下的问题不是当您执行flatMap 时类型会丢失。 Result[A]Either[DecodingFailure, A] 类型实际上是相同的,Scala 编译器知道这一点。问题是Either[_,_] 类型的这个重要的 split 丢失了,Scala 编译器不够聪明,无法自行重新生成.
    猜你喜欢
    • 2017-12-29
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 2020-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多