取自 Programming Scala 书中的类型类示例:

case class Address(street: String, city: String) 
case class Person(name: String, address: Address) 
 
trait ToJSON { 
  def toJSON(level: Int = 0): String 
 
  val INDENTATION = "  " 
  def indentation(level: Int = 0): (String,String) =  
    (INDENTATION * level, INDENTATION * (level+1)) 
} 
 
implicit class AddressToJSON(address: Address) extends ToJSON { 
  def toJSON(level: Int = 0): String = { 
    val (outdent, indent) = indentation(level) 
    s"""{ 
      |${indent}"street": "${address.street}",  
      |${indent}"city":   "${address.city}" 
      |$outdent}""".stripMargin 
  } 
} 
 
implicit class PersonToJSON(person: Person) extends ToJSON { 
  def toJSON(level: Int = 0): String = { 
    val (outdent, indent) = indentation(level) 
    s"""{ 
      |${indent}"name":    "${person.name}",  
      |${indent}"address": ${person.address.toJSON(level + 1)}  
      |$outdent}""".stripMargin 
  } 
} 
 
val a = Address("1 Scala Lane", "Anytown") 
val p = Person("Buck Trends", a) 
 
println(a.toJSON()) 
println() 
println(p.toJSON()) 

代码工作正常,但我的印象(来自一些博客文章)类型类通常在 scala 中以这种方式完成:
// src/main/scala/progscala2/implicits/toJSON-type-class.sc 
 
case class Address(street: String, city: String) 
case class Person(name: String, address: Address) 
 
trait ToJSON[A] { 
  def toJSON(a: A, level: Int = 0): String 
 
  val INDENTATION = "  " 
  def indentation(level: Int = 0): (String,String) = 
    (INDENTATION * level, INDENTATION * (level+1)) 
} 
 
object ToJSON { 
  implicit def addressToJson: ToJSON[Address] = new ToJSON[Address] { 
    override def toJSON(address: Address, level: Int = 0) : String = { 
          val (outdent, indent) = indentation(level) 
          s"""{ 
              |${indent}"street": "${address.street}", 
              |${indent}"city":   "${address.city}" 
              |$outdent}""".stripMargin 
    } 
  } 
  implicit def personToJson: ToJSON[Person] = new ToJSON[Person] { 
    override def toJSON(a: Person, level: Int): String = { 
          val (outdent, indent) = indentation(level) 
          s"""{ 
              |${indent}"name":    "${a.name}", 
              |${indent}"address": ${implicitly[ToJSON[Address]].toJSON(a.address, level + 1)} 
              |$outdent}""".stripMargin 
    } 
  } 
  def toJSON[A](a: A, level: Int = 0)(implicit ev: ToJSON[A]) = { 
    ev.toJSON(a, level) 
  } 
} 
 
 
val a = Address("1 Scala Lane", "Anytown") 
val p = Person("Buck Trends", a) 
 
 
import ToJSON.toJSON 
println(toJSON(a)) 
println(toJSON(p)) 

哪种方式更好或更正确?欢迎任何见解。

请您参考如下方法:

调用第一个 ToJSON 有点牵强完全是“类型类”(尽管这些术语并不是标准化的,甚至您的第二个更符合 Scala 惯用语的版本在许多重要方面也与 Haskell 中的类型类不同)。

我认为定义的类型类的属性之一是它们允许您约束泛型类型。 Scala 以上下文边界的形式提供了特殊的语法来支持这一点,所以我可以编写例如以下:

import io.circe.Encoder 
 
def foo[A: Numeric: Encoder](a: A) = ... 

这限制了类型 A拥有两个 NumericEncoder实例。

此语法不适用于第一个 ToJSON ,并且您必须使用诸如 View 边界(现已弃用)或隐式隐式转换参数之类的东西。

还有很多种操作是第一个 ToJSON无法提供的风格。例如,假设我们有一个 Monoid使用类型类的标准 Scala 编码:
trait Monoid[A] { 
  def empty: A 
  def plus(x: A, y: A): A 
} 

我们想把它翻译成第一个样式,我们有一个未参数化的 Monoid trait 将成为我们希望能够视为 monoidal 的类型的隐式转换的目标。我们完全不走运,因为我们没有可以引用我们的 empty 的类型参数。和 plus签名。

另一个论点:标准库中的类型类( OrderingCanBuildFrom 等)都使用第二种样式,您会遇到的绝大多数第三方 Scala 库也是如此。

简而言之,永远不要使用第一个版本。只有当您只有 A => Whatever 形式的操作时,它才会起作用。 (对于一些具体的 Whatever ),没有很好的语法支持,并且通常不被社区认为是惯用的。


评论关闭
IT干货网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!