在使用Type類模式的Scala項目中工作時,我遇到了語言如何實現該模式的嚴重問題:由於Scala類型的實現必須由程序員而不是語言,任何屬於類型類的變量都不能被註釋爲父類型,除非它的類型類實現被使用。概述Scala類型類的問題
爲了說明這一點,我編寫了一個快速示例程序。想象一下,你正試圖編寫一個程序來處理公司不同類型的員工,並可以打印他們的進度報告。與Scala的類型類模式解決這個問題,你可以嘗試這樣的事:
abstract class Employee
class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
class Shipper(trucksShipped: Int) extends Employee
類層次結構模型不同的員工,很簡單。現在我們實現ReportMaker類型的類。
trait ReportMaker[T] {
def printReport(t: T): Unit
}
implicit object PackerReportMaker extends ReportMaker[Packer] {
def printReport(p: Packer) { println(p.boxesPacked + p.cratesPacked) }
}
implicit object ShipperReportMaker extends ReportMaker[Shipper] {
def printReport(s: Shipper) { println(s.trucksShipped) }
}
這一切都很好,我們現在可以寫一些類型的名冊類的看起來像這樣的:
class Roster {
private var employees: List[Employee] = List()
def reportAndAdd[T <: Employee](e: T)(implicit rm: ReportMaker[T]) {
rm.printReport(e)
employees = employees :+ e
}
}
所以此工程。現在,由於我們的類型,我們可以將一個打包器或託運對象傳遞給reportAndAdd方法,它將打印報告並將該員工添加到名冊中。然而,如果不明確地存儲傳遞給reportAndAdd的rm對象,那麼編寫一個試圖打印出名單中每個員工報告的方法將是不可能的。
支持該模式的其他兩種語言Haskell和Clojure不會分享這個問題,因爲他們處理這個問題。 Haskell存儲從數據類型到全局實現的映射,所以它總是與'變量'一起使用,而Clojure基本上是做同樣的事情。這是一個在Clojure中完美運行的簡單例子。
(defprotocol Reporter
(report [this] "Produce a string report of the object."))
(defrecord Packer [boxes-packed crates-packed]
Reporter
(report [this] (str (+ (:boxes-packed this) (:crates-packed this)))))
(defrecord Shipper [trucks-shipped]
Reporter
(report [this] (str (:trucks-shipped this))))
(defn report-roster [roster]
(dorun (map #(println (report %)) roster)))
(def steve (Packer. 10 5))
(def billy (Shipper. 5))
(def roster [steve billy])
(report-roster roster)
除了開啓員工列表爲類型列表中,而討厭方案(員工,ReportMaker [僱員]),並提供斯卡拉任何方式來解決這個問題?如果不是,由於Scala庫大量使用類型類,爲什麼它沒有得到解決?
這又是的亞型搞亂了一切另一個例子。如果你認爲你的員工子類是構造函數(從Haskell的意義上)而不是子類型,你會發現類型類方法更舒適。 –
我不確定Haskell會如何解決這個問題。 Haskell的標準列表類型不是異構的,所以所有元素都會有相同的類型實例。將不同類型的「Packer」和「Shipper」放在同一個列表中的想法是行不通的。 –