Scala类型类的泛化问题
在处理使用类型类模式的Scala项目时,我遇到了语言如何实现模式的严重问题:因为Scala类型类的实现必须由程序员而不是语言来管理,任何属于类型类的变量都不能被注释为父类型,除非它的类型类实现与之一起使用 为了说明这一点,我编写了一个快速示例程序。想象一下,您正试图编写一个程序,该程序可以为一家公司处理不同类型的员工,并可以打印他们的进度报告。要使用Scala中的类型类模式解决此问题,可以尝试以下方法:Scala类型类的泛化问题,scala,typeclass,Scala,Typeclass,在处理使用类型类模式的Scala项目时,我遇到了语言如何实现模式的严重问题:因为Scala类型类的实现必须由程序员而不是语言来管理,任何属于类型类的变量都不能被注释为父类型,除非它的类型类实现与之一起使用 为了说明这一点,我编写了一个快速示例程序。想象一下,您正试图编写一个程序,该程序可以为一家公司处理不同类型的员工,并可以打印他们的进度报告。要使用Scala中的类型类模式解决此问题,可以尝试以下方法: abstract class Employee class Packer(boxesPack
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
和的想法同一个列表中的code>不起作用。这并不能解决问题,因为正如您所说,只有当您知道以后有人需要添加更多数据类型时,才使用类型类。例如,员工代码的作者可能会认为稍后有人需要添加经理类。但是,由于您正在使用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""")))
}