Scala类型类的泛化问题

Scala类型类的泛化问题,scala,typeclass,Scala,Typeclass,在处理使用类型类模式的Scala项目时,我遇到了语言如何实现模式的严重问题:因为Scala类型类的实现必须由程序员而不是语言来管理,任何属于类型类的变量都不能被注释为父类型,除非它的类型类实现与之一起使用 为了说明这一点,我编写了一个快速示例程序。想象一下,您正试图编写一个程序,该程序可以为一家公司处理不同类型的员工,并可以打印他们的进度报告。要使用Scala中的类型类模式解决此问题,可以尝试以下方法: abstract class Employee class Packer(boxesPack

在处理使用类型类模式的Scala项目时,我遇到了语言如何实现模式的严重问题:因为Scala类型类的实现必须由程序员而不是语言来管理,任何属于类型类的变量都不能被注释为父类型,除非它的类型类实现与之一起使用

为了说明这一点,我编写了一个快速示例程序。想象一下,您正试图编写一个程序,该程序可以为一家公司处理不同类型的员工,并可以打印他们的进度报告。要使用Scala中的类型类模式解决此问题,可以尝试以下方法:

abstract class Employee
class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
class Shipper(trucksShipped: Int) extends Employee
class Roster {
    private var employees: List[Employee] = List()

    def reportAndAdd[T <: Employee](e: T)(implicit rm: ReportMaker[T]) {
       rm.printReport(e)
       employees = employees :+ e
    }
}
一个类层次结构建模不同类型的员工,非常简单。现在我们实现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) }
}
这一切都很好,我们现在可以编写一些类似这样的花名册类:

abstract class Employee
class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
class Shipper(trucksShipped: Int) extends Employee
class Roster {
    private var employees: List[Employee] = List()

    def reportAndAdd[T <: Employee](e: T)(implicit rm: ReportMaker[T]) {
       rm.printReport(e)
       employees = employees :+ e
    }
}

除了将员工列表转换为类型列表[(employee,ReportMaker[employee])这一相当糟糕的解决方案之外,Scala提供了解决这个问题的方法吗?如果没有,既然Scala库大量使用类型类,为什么还没有解决呢?

通常在Scala中实现代数数据类型的方法是使用
case
类:

sealed trait Employee
case class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
case class Shipper(trucksShipped: Int) extends Employee
这为
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)       => // ...
  }
}
如果没有其他类型需要使用
ReportMaker
实例,那么可能不需要type类,您可以使用
printReport
函数

但是,如果不显式存储传递给reportAndAdd!的rm对象,编写一个试图打印出花名册中每个员工的报告的方法是不可能的

不确定您的确切问题。以下应该可以工作(显然,对于在I/O输出点连接的单独报告):

(插入与Q类似的隐式对象…)

def打印报告(ls:List[Employee])={

def generateReport[T这是另一个子类型混乱的例子你会发现类型类方法比子类型更合适。我不确定Haskell是否会像你想象的那样解决这个问题。Haskell的标准列表类型不是异构的,所以所有元素都会有相同的类型类实例。放置不同类型的
Packer
的想法不起作用。这并不能解决问题,因为正如您所说,只有当您知道以后有人需要添加更多数据类型时,才使用类型类。例如,员工代码的作者可能会认为稍后有人需要添加经理类。但是,由于您正在使用g模式匹配为了解决这个问题,他不能添加类,除非他修改库代码本身。他可能无法这样做。
trait ReportMaker[T] {
  def generateReport(t: T): String
}
def printReport(ls: List[Employee]) = {
  def generateReport[T <: Employee](e: T)(implicit rm: ReportMaker[T]): String = rm.generateReport(e)
  // trivial example with string concatenation - but could do any fancy combine :)
  someIOManager.print(ls.map(generateReport(_)).mkString("""\n""")))
}