【问题标题】:Polymorphism with Scala type classesScala 类型类的多态性
【发布时间】:2013-12-23 03:52:13
【问题描述】:

我们正在重构继承的method 以改用类型类——我们希望将所有method 实现集中在一个地方,因为将它们分散在实现类中会使维护变得困难。然而,我们遇到了一些麻烦,因为我们对类型类还很陌生。目前method定义为

trait MethodTrait {
  def method: Map[String, Any] = // default implementation
}

abstract class SuperClass extends MethodTrait {
  override def method = super.method ++ // SuperClass implementation
}

class Clazz extends SuperClass {
  override def method = super.method ++ // Clazz implementation
}

等等,总共有 50 多个具体类,层次结构相当浅(abstract class SuperClass -> abstract class SubSuperClass -> abstract class SubSubSuperClass -> class ConcreteClass 尽可能深),一个具体的类永远不会扩展另一个具体的类。 (在实际实现中,method 返回一个 Play Framework JsObject 而不是 Map[String, Any]。)我们正在尝试将其替换为类型类:

trait MethodTrait[T] {
  def method(target: T): Map[String, Any]
}

class MethodType {
  type M[T] = MethodTrait[T]
}

implicit object Clazz1Method extends MethodTrait[Clazz1] {
  def method(target: Clazz1): Map[String, Any] { ... }
}

implicit object Clazz2Method extends MethodTrait[Clazz2] {
  def method(target: Clazz2): Map[String, Any] { ... }
}

// and so on

我遇到了两个问题:

一个。模仿先前实现中的super.method ++ 功能。目前我正在使用

class Clazz1 extends SuperClass

class Clazz2 extends SubSuperClass

private def superClassMethod(s: SuperClass): Map[String, Any] = { ... }

private def subSuperClassMethod(s: SubSuperClass): Map[String, Any] = {
  superClassMethod(s) ++ ...
}

implicit object Clazz1Method extends MethodTrait[Clazz1] {
  def method(target: Clazz1): Map[String, Any] = { 
    superClassMethod(target) ++ ... 
  }
}

implicit object Clazz2Method extends MethodTrait[Clazz2] {
  def method(target: Clazz2): Map[String, Any] = { 
    subSuperClassMethod(target) ++  ... 
  }
}

但这很丑陋,如果我不小心调用了层次结构太远的方法,我不会收到警告或错误,例如如果Clazz2 调用superClassMethod 而不是subSuperClassMethod

B。在超类上调用 method,例如

val s: SuperClass = new Clazz1()
s.method

理想情况下,我希望能够告诉编译器 SuperClass 的每个子类在类型类中都有一个对应于 method 的隐式对象,因此 s.method 是类型安全的(或者我会得到如果我忽略了为SuperClass 的子类实现相应的隐式对象,则会出现编译时错误),但我所能想到的只是

implicit object SuperClassMethod extends MethodTrait[SuperClass] {
  def method(target: SuperClass): Map[String, Any] = { 
    target match {
      case c: Clazz1 => c.method
      case c: Clazz2 => c.method
      ...
    }
  }
}

这很丑陋,如果我省略了一个类,它不会给我编译时警告或错误,因为我无法将 SuperClass 定义为密封特征。


我们对类型类的替代方案持开放态度,这将使我们能够将method 代码集中在一个地方。 method 只能从两个地方调用:

A.其他method 实现,例如Clazz1 有一个val clazz2: Option[Clazz2],在这种情况下Clazz1 中的method 实现将类似于

def method = super.method ++ /* Clazz1 method implementation */ ++ 
  clazz2.map(_.method).getOrElse(Map())

B.顶级 Play Framework 控制器(即所有控制器继承的抽象类),我们在其中定义了三个 ActionBuilders,它们调用 method,例如

def MethodAction[T <: MethodTrait](block: Request[AnyContent] => T) = {
  val f: Request[AnyContent] => SimpleResult = 
    (req: Request[AnyContent]) => Ok(block(req).method)

  MethodActionBuilder.apply(f)
}

【问题讨论】:

    标签: oop scala functional-programming polymorphism typeclass


    【解决方案1】:

    简单地说:如果你想在一个地方实现,你应该为你的层次结构使用案例类:

    abstract class SuperClass;
    
    case class Clazz(...) extends SuperClass;
    
    def method(o : SuperClass) : Map[String, Any] = o match {
       case Clazz( ... ) => defaultMethod ++ ...
       case ...
    } 
    

    (请注意,方法当然可以是递归的) 由于您可以在 scala 中使用 open Sum 类型(不过,编译器不会警告缺少模式),这应该可以解决您的问题,而不必滥用类型类。

    【讨论】:

      【解决方案2】:

      @0__ 有点意思——编译时会发生隐式解析,因此用于给定输入的类型类实例将依赖于该输入的运行时类型。

      要获得您想要的行为,您需要编写一些隐式定义,以反映您要调用 method 以选择正确的类型类实例的对象的实际类型。

      我认为这更像是一个维护问题,而不是你现在遇到的问题。

      【讨论】:

        【解决方案3】:

        我认为类型类与您的方案不兼容。它们在类型不相交时很有用,但您实际上要求实例反映超类型/子类型层次结构并且不是独立的。

        通过这种重构,您只是在制造选择错误实例的危险:

        trait Foo
        case class Bar() extends Foo
        
        trait HasBaz[A] { def baz: Set[Any] }
        
        implicit object FooHasBaz extends HasBaz[Foo] { def baz = Set("foo") }
        implicit object BarHasBaz extends HasBaz[Bar] { def baz = FooHasBaz.baz + "bar" }
        
        def test[A <: Foo](x: A)(implicit hb: HasBaz[A]): Set[Any] = hb.baz
        
        val bar: Foo = Bar()
        test(bar) // boom!
        

        所以你最终用SuperClassMethod 中的模式匹配器重写了多态调度。您基本上是 OO -> FP -> OO,同时呈现类型类不可用(要打开)的想法,最终以 sum 类型(所有子类型都已知)。

        【讨论】:

          猜你喜欢
          • 2018-08-14
          • 2016-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-20
          相关资源
          最近更新 更多