Scala 类型类理解接口语法

spa*_*rkr 3 scala interface typeclass scala-cats

我正在阅读有关猫的信息,并且遇到了以下代码片段,该代码片段是关于将对象序列化为 JSON 的!

它以这样的特征开始:

trait JsonWriter[A] {
  def write(value: A): Json
}
Run Code Online (Sandbox Code Playgroud)

在此之后,我们的域对象有一些实例:

final case class Person(name: String, email: String)

object JsonWriterInstances {
  implicit val stringWriter: JsonWriter[String] =
    new JsonWriter[String] {
      def write(value: String): Json =
        JsString(value)
    }

  implicit val personWriter: JsonWriter[Person] =
    new JsonWriter[Person] {
      def write(value: Person): Json =
        JsObject(Map(
          "name" -> JsString(value.name),
          "email" -> JsString(value.email)
        ))
    }
  // etc...
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好!然后我可以这样使用它:

import JsonWriterInstances._
Json.toJson(Person("Dave", "dave@example.com"))
Run Code Online (Sandbox Code Playgroud)

后来我遇到了一种叫做接口语法的东西,它使用扩展方法来扩展现有类型的接口方法,如下所示:

object JsonSyntax {
  implicit class JsonWriterOps[A](value: A) {
    def toJson(implicit w: JsonWriter[A]): Json =
      w.write(value)
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,这将序列化 Person 的调用简化为:

import JsonWriterInstances._
import JsonSyntax._
Person("Dave", "dave@example.com").toJson
Run Code Online (Sandbox Code Playgroud)

我不明白的是,如何将 Person 装箱到 JsonWriterOps 中,以便我可以直接调用 toJson,就好像 toJson 是在 Person 案例类本身中定义的一样。我喜欢这种魔法,但我无法理解关于 JsonWriterOps 的最后一步。那么这个接口语法背后的想法是什么,它是如何工作的?有什么帮助吗?

小智 5

这实际上是一个标准的 Scala 特性,因为JsonWriterOps被标记implicit并且在范围内,编译器可以在需要时在编译时应用它。因此 scalac 将进行以下转换:

Person("Dave", "dave@example.com").toJson
new JsonWriterOps(Person("Dave", "dave@example.com")).toJson
new JsonWriterOps[Person](Person("Dave", "dave@example.com")).toJson
Run Code Online (Sandbox Code Playgroud)

旁注

像这样将隐式类作为值类更有效:

implicit class JsonWriterOps[A](value: A) extends AnyVal
Run Code Online (Sandbox Code Playgroud)

这使得编译器也优化掉了新的对象构造,如果可能的话,将整个隐式转换+方法调用编译为一个简单的函数调用。