scala - 如何重载类型类的产品方法

标签 scala typeclass overloading generic-programming shapeless

我正在尝试使用 Shapeless 中的自动类型类派生机制来实现各种 ReadJsonCodec。

这是我的 ReadCodecCompanionObject:

object ReadCodec extends LabelledProductTypeClassCompanion[ReadCodec] {
  implicit object StringCodec extends SimpleCodec[String] {
    def read(j: Json): String = j.stringOr(throw ...)
  }
  implicit object IntCodec ...
  implicit object BooleanCodec ...
  implicit object LongCodec ...
  implicit object ShortCodec ...
  implicit object DoubleCodec ...
  implicit object BigDecimalCodec ...

  implicit def readCodecInstance: LabelledProductTypeClass[ReadCodec] = new LabelledProductTypeClass[ReadCodec] {
    def emptyProduct = new ReadCodec[HNil] {
      // This will silently accept extra fields within a JsonObject
      // To change this behavior make sure json is a JsonObject and that it is empty
      def read(json: Json) = HNil
    }

    def product[F, T <: HList](name: String, FHead: ReadCodec[F], FTail: ReadCodec[T]) = new ReadCodec[F :: T] {
      def read(json: Json): F :: T = {
        val map = castOrThrow(json)
        val fieldValue = map.getOrElse(name, throw new MappingException(s"Expected field $name on JsonObject $map"))
        // Try reading the value of the field
        // If we get a mapping exception, intercept it and add the name of this field to the path
        // If we get another exception, don't touch!
        // Pitfall: if handle did not accept a PartialFunction, we could transform an unknow exception into a match exception
        val head: F = Try(FHead.read(fieldValue)).handle{ case MappingException(msg, path) => throw MappingException(msg, s"$name/$path")}.get
        val tail = FTail.read(json)
        head :: tail
      }
    }

    def product[A, T <: HList](name: String, FHead: ReadCodec[Option[A]], FTail: ReadCodec[T]) = new ReadCodec[Option[A] :: T] {
      def read(json: Json): Option[A] :: T = {
        val map = castOrThrow(json)
        val head: Option[A] = map.get(name).map { fieldValue =>
          Try(FHead.read(fieldValue)).handle{ case MappingException(msg, path) => throw MappingException(msg, s"$name/$path")}.get.get
        }
        val tail = FTail.read(json)
        head :: tail
      }
    }

    def project[F, G](instance: => ReadCodec[G], to : F => G, from : G => F) = new ReadCodec[F] {
      def read(json: Json): F = from(instance.read(json))
    }
  }
}

这是一段复杂的代码,可以快速掌握,但一旦你理解了它就非常简单。重要的部分是两个 def product方法。我遇到的问题是,如果该字段将映射到类型为 Option[A] 的值,我希望此编解码器接受缺少字段的 Json AST .这意味着我需要 product 函数来知道 HList 的头部是否为 Option[A] 类型。

具体来说:
case class Foo(a: String, b: Option[Boolean])
val test = read[Foo](json"""{"a" : "Beaver"}""") // should work

但是目前这会失败,因为它对 Option 没有任何区别,并且需要一个字段 b .我尝试了两种解决方法,但都没有奏效。

第一个也是最干净的一个是使用 F 类型参数替换为 Option[A] 的版本重载 product 方法。这种方法相当干净,尽管我不确定它是否能很好地与无形派生宏配合使用。但是,这是不可能的,因为当删除后的类型签名相同时,Scala 不支持重载函数的能力。这是上面的版本。不幸的是,目前不能使用 Scala 编译器进行编译。

第二种方法是使用 TypeTag 在运行时确定 F 是否为 Option[_] 类型并表现得适当。这个版本几乎肯定会不那么优雅并且涉及 Actor ,但我可以忍受它。但是,这似乎是不可能的,因为添加 TypeTag 会更改产品方法的签名(添加隐式参数),然后编译器会提示我没有定义抽象方法产品。

有没有人对进行的最佳方式有任何建议。

最佳答案

您需要获得一个类型类,其中包含有关 F 的信息。通过传递。但是你已经在传递一个类型类,形式为 ReadCodec .因此,解决方案是将其替换为包含您需要的所有信息的内容:

trait ReadCodecAndTypeTag[A] {
  val rc: ReadCodec[A]
  val tt: TypeTag[A]
}

但在这种情况下,您也可以将decode-from-optional-value-in-a-map 也委托(delegate)给这个类型类:
trait OReadCodec[A] {
  val rc: ReadCodec[A]
  def missingField(name: String, map: Any): A =
    throw new MappingException(s"Expected field $name on JsonObject $map")
}
implicit object StringCodec extends OReadCodec[String] {
  val rc = new ReadCodec[String] {...}
}
implicit object IntCodec ...
...
implicit def OptionCodec[A](implicit orc: OReadCodec[A]) =
 new OReadCodec[Option[A]] {
  val rc = ...
  override def missingField(name: String, map: Any) = None
}
...
def product[F, T <: HList](name: String, FHead: OReadCodec[F], FTail: OReadCodec[T]) =
  new OReadCodec[F :: T] {
    val rc = new ReadCodec[F :: T] {
      def read(json: Json): F :: T = {
      val map = castOrThrow(json)
      val fieldValue = map.getOrElse(name, FHead.missingField(name, map))
      val head: F = ...
      ...
    }
  }
}

implicit def ReadCodecFromOReadCodec[A](implicit orc: OReadCodec[A]) = orc.rc

关于scala - 如何重载类型类的产品方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25436207/

相关文章:

scala - Scala 的 Java EE 替代品?

haskell - 函数和克莱斯利箭头的应用

java - 为什么 Guava 的 ImmutableList 有这么多重载的 of() 方法?

c# - 我可以定义一个方法来接受 Func<T> 或 Expression<Func<T>> 吗?

scala - 如何分解带有前缀的结构列?

scala - 猫效应 : How to transform `List[IO]` to `IO[List]`

scala - 模拟 Scala 对象

haskell - 类型类和重载,有什么联系?

haskell - 根据类型的具体性重建惰性列表?

C++ 方法重载 : base and derived parameters