概括Scala类型的问题

DrP*_*per 6 scala typeclass

在处理使用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,为什么还没有解决它?

Ben*_*mes 5

您通常在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)

这为图案提取PackerShipper构造函数,所以你可以匹配他们.

不幸的是,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函数即可.