首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Scala构建类型库的不同方法?

用Scala构建类型库的不同方法?
EN

Stack Overflow用户
提问于 2016-02-29 16:47:13
回答 1查看 294关注 0票数 5

一个类型化的例子摘自编程Scala的书:

代码语言:javascript
复制
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中,典型的类型分类是这样的:

代码语言:javascript
复制
// 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))

哪种方法更好或者更正确?任何见解都是欢迎的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-02-29 17:11:01

将第一个ToJSON称为“类型类”(虽然这些术语并不是标准化的,甚至您的第二个、更熟悉Scala的版本在许多重要方面都不同于Haskell中的类型类),这是很难做到的。

我认为类型类的一个属性是它们允许约束泛型类型。Scala提供了支持上下文边界形式的特殊语法,因此我可以编写如下内容:

代码语言:javascript
复制
import io.circe.Encoder

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

这就限制了A类型同时具有NumericEncoder实例。

对于第一个ToJSON,这个语法是不可用的,您必须使用类似视图边界(现在不再推荐)或隐式隐式转换参数之类的方法。

还有许多类型的操作无法由第一种ToJSON样式提供。例如,假设我们有一个Monoid,它使用了类型类的标准Scala编码:

代码语言:javascript
复制
trait Monoid[A] {
  def empty: A
  def plus(x: A, y: A): A
}

我们想把它转化为第一种风格,在那里,我们有一个非参数化的Monoid特征,它将成为从我们想要处理为单面的类型的隐式转换的目标。我们完全不走运,因为我们没有可以引用emptyplus签名的类型参数。

另一个参数:标准库中的类型类(OrderingCanBuildFrom等)它们都使用第二种样式,就像您将遇到的绝大多数第三方Scala库一样。

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

票数 18
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35704933

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档