在处理使用类型类模式的 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) }
}
这一切都很好,我们现在可以编写某种可能如下所示的 Roster 类:
class Roster {
private var employees: List[Employee] = List()
def reportAndAdd[T <: Employee](e: T)(implicit rm: ReportMaker[T]) {
rm.printReport(e)
employees = employees :+ e
}
}
所以这行得通。现在,由于我们的 type-class,我们可以将 packer 或 shipper 对象传递给 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)
除了将员工列表转换为 List[(Employee, ReportMaker[Employee]) 类型的相当讨厌的解决方案之外,Scala 是否提供任何解决此问题的方法?如果没有,既然 Scala 库广泛使用类型类,为什么没有解决它?