2016-02-29 32 views
5

一个类型类从编程斯卡拉书所采取的示例:在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()) 

的代码工作正常,但我的印象(一些博客文章),该类型类通常做这样的斯卡拉:

// 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

你也许可以在程序员社区中问这个问题:http://programmers.stackexchange.com/ – ManoDestra

+1

@ManoDestra - 这可能会被程序员关闭,主要是基于观点。这种问题的很多(大部分)问题都归结为“选择一种方法并保持一致”的答案。 – GlenH7

+0

确实如此。看起来更像是一个编程特定的问题,而不是一个可靠的实现。 – ManoDestra

回答

18

将第一个ToJSON称为“类型类”根本不算什么(尽管它不像这些术语是标准化的,甚至你的第二个更具Scala习惯的版本在许多重要方面与Haskell中的类型类有所不同)。

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

import io.circe.Encoder 

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

这限制了类型A兼得NumericEncoder实例。

该语法不适用于第一个ToJSON,您必须使用类似视图边界(现已弃用)或隐式隐式转换参数。

也有许多种不能通过第一种ToJSON风格提供的操作。例如,假设我们有一个使用类型类的标准Scala编码一个Monoid

trait Monoid[A] { 
    def empty: A 
    def plus(x: A, y: A): A 
} 

,我们希望把它翻译成的第一个样式,在这里我们有一个unparametrized Monoid特质,这将是目标我们希望能够将其视为monoidal的类型进行隐式转换。我们完全没有运气,因为我们没有可以参考我们的emptyplus签名的类型参数。

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

总之,千万不要使用第一个版本。只有当您只有A => Whatever(某些具体的Whatever)形式的操作,没有很好的语法支持,并且通常不被社区视为习惯用法时,它才会起作用。