在处理使用Type Class模式的Scala项目时,我遇到了语言如何实现模式的严重问题:由于Scala类型类实现必须由程序员而不是语言管理,因此任何变量属于类类的类永远不会被注释为父类型,除非它采用类型类实现.
为了说明这一点,我编写了一个快速示例程序.想象一下,您正在尝试编写一个程序,可以为公司处理不同类型的员工,并可以打印有关其进度的报告.要使用Scala中的类型类模式解决此问题,您可以尝试这样的方法:
abstract class Employee
class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
class Shipper(trucksShipped: Int) extends Employee
Run Code Online (Sandbox Code Playgroud)
一个类层次结构模拟不同类型的员工,足够简单.现在我们实现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) }
}
Run Code Online (Sandbox Code Playgroud)
这一切都很好,我们现在可以编写一些可能如下所示的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
}
}
Run Code Online (Sandbox Code Playgroud)
这样可行.现在,由于我们的类型类,我们可以将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)
Run Code Online (Sandbox Code Playgroud)
除了将员工列表转换为List [(Employee,ReportMaker [Employee])类型的相当讨厌的解决方案之外,Scala是否提供了解决此问题的任何方法?如果没有,由于Scala库大量使用Type-Classes,为什么还没有解决它?
您通常在Scala中实现代数数据类型的方式是使用case类:
sealed trait Employee
case class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
case class Shipper(trucksShipped: Int) extends Employee
Run Code Online (Sandbox Code Playgroud)
这为图案提取Packer和Shipper构造函数,所以你可以匹配他们.
不幸的是,Packer并且Shipper也是不同的(子)类型,但是在Scala中编码代数数据类型的模式的一部分是要遵守忽略它的规则.相反,在区分打包器或出货单时,使用模式匹配,就像在Haskell中一样:
implicit object EmployeeReportMaker extends ReportMaker[Employee] {
def printReport(e: Employee) = e match {
case Packer(boxes, crates) => // ...
case Shipper(trucks) => // ...
}
}
Run Code Online (Sandbox Code Playgroud)
如果您没有其他需要ReportMaker实例的类型,那么可能不需要类型类,您只需使用该printReport函数即可.
| 归档时间: |
|
| 查看次数: |
639 次 |
| 最近记录: |