在使用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”放在同一个列表中的想法是行不通的。 –