来自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))
哪种方式更好或更正确?任何见解都是受欢迎的。
答案 0 :(得分:18)
可以拨打第一个ToJSON
a"类型类"完全没有(虽然它们并不像这些术语是标准化的,甚至你的第二个,更多的Scala-idiomatic版本在许多重要方面与Haskell中的类型类不同)。
我认为定义的类型类的一个属性是它们允许您约束泛型类型。 Scala提供了特殊的语法来以上下文边界的形式支持它,所以我可以写例如以下内容:
import io.circe.Encoder
def foo[A: Numeric: Encoder](a: A) = ...
这会将A
类型约束为同时拥有Numeric
和Encoder
个实例。
此语法不适用于第一个ToJSON
,您必须使用视图边界(现已弃用)或隐式隐式转换参数等。
第一种ToJSON
风格也无法提供多种操作。例如,假设我们有Monoid
使用类型类的标准Scala编码:
trait Monoid[A] {
def empty: A
def plus(x: A, y: A): A
}
我们希望将其转换为第一种样式,我们有一个非参数化的Monoid
特征,它将成为我们希望能够将其视为单一形式的类型的隐式转换的目标。我们完全没有运气,因为我们没有类型参数可以参考我们的empty
和plus
签名。
另一个论点:标准库中的类型类(Ordering
,CanBuildFrom
等)都使用第二种样式,绝大多数第三方Scala库都使用#39我会遇到。
简而言之,不要使用第一个版本。它只适用于只有A => Whatever
形式的操作(对于某些具体的Whatever
),没有很好的句法支持,并且通常不被认为是惯用的由社区。 p>