2016-02-29 68 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)形式的操作,沒有很好的語法支持,並且通常不被社區視爲習慣用法時,它纔會起作用。