多态性与Scala类型的类 [英] Polymorphism with Scala type classes

查看:83
本文介绍了多态性与Scala类型的类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在重构一个继承的方法来代替使用类型类 - 我们想集中所有的方法实现在一个地方,因为让它们分散在实施类中会使维护变得困难。然而,我们遇到了一些麻烦,因为我们对输入类很新。目前方法定义为

  trait MethodTrait {
def方法:Map [String,Any] = //默认实现
}

抽象类SuperClass extends MethodTrait {
override def method = super.method ++ // SuperClass implementation
}

class Clazz扩展SuperClass {
覆盖def method = super.method ++ // Clazz实现
}

等等,其中总共有50多个具体类,层次结构相当浅( abstract class SuperClass - > 抽象类SubSuperClass - > 抽象类SubSubSuperClass - > 类ConcreteClass 就像它一样深),具体的类永远不会扩展另一个具体的类。 (在实际的实现中, method 返回一个Play框架 JsObject 而不是 Map [String ,任何] 。)我们试图用一个类型类替换它:

  trait MethodTrait [T] {
def method(target:T):Map [String,Any]
}
$ b $ class MethodType {
type M [T] = MethodTrait [ T]
}

隐式对象Clazz1Method extends MethodTrait [Clazz1] {
def method(target:Clazz1):Map [String,Any] {...}
}

隐式对象Clazz2Method扩展MethodTrait [Clazz2] {
def method(target:Clazz2):Map [String,Any] {...}
}

//等等

我遇到两个问题:



A 。模拟前面实现中的 super.method ++ 功能。目前我正在使用

  class Clazz1 extends SuperClass 
$ b $ class Clazz2 extends SubSuperClass

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

隐式对象Clazz1Method extends MethodTrait [Clazz1] {
def method(target:Clazz1):Map [String ,任意] = {
superClassMethod(target)++ ...
}
}

隐式对象Clazz2Method extends MethodTrait [Clazz2] {
def方法(target:Clazz2):Map [String,Any] = {
subSuperClassMethod(target)++ ...
}
}

但这很丑陋,如果我意外地调用了一个方法太离谱的方法,我不会收到警告或错误,例如如果 Clazz2 调用 superClassMethod 而不是 subSuperClassMethod



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

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

理想情况下,我希望能够告诉编译器对于 SuperClass 的每个子类都有一个相应的隐式对象,用于类型类中的 method ,所以 s.method 是类型安全的(或者如果我忽略为 SuperClass ),但是所有我能够想到的是

 隐式对象SuperClassMethod扩展了MethodTrait [SuperClass ] {
def method(target:SuperClass):Map [String,Any] = {
target match {
case c:Clazz1 => c.method
case c:Clazz2 => c.method
...
}
}
}

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




我们可以选择类型类来让我们集中方法代码在一个地方。 方法仅在两个地方被调用:

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

  def method = super.method ++ / * Clazz1方法实现* / ++ 
clazz2.map(_。method).getOrElse Map())

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

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

MethodActionBuilder.apply(f)
}


解决方案

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



通过此重构,你只是创建了被挑选错误实例的危险:

pre $ $ $ $ c $ trait $ Foo

trait HasBaz [A] {def baz:Set [Any]}

隐式对象FooHasBaz扩展HasBaz [Foo] {def baz = Set(foo)}
隐式对象BarHasBaz extends HasBaz [Bar] {def baz = FooHasBaz.baz +bar}

def test [A< ;: Foo](x:A)(隐式hb: HasBaz [A]):设置[Any] = hb.baz

val bar:Foo = Bar()
test(bar)// boom!

所以你最终用模式匹配器重新编写了多态调度, SuperClassMethod 。你基本上是OO - > FP - > OO,而类型类的概念不可用(将被打开),最终以sum类型(所有已知的子类型)结束。


We are refactoring an inherited method to use a type class instead - we would like to concentrate all of the method implementations in one place, because having them scattered among the implementing classes is making maintenance difficult. However, we're running into some trouble as we're fairly new to type classes. At present method is defined as

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
}

and so on, where there are a total of 50+ concrete classes, the hierarchy is fairly shallow (abstract class SuperClass -> abstract class SubSuperClass -> abstract class SubSubSuperClass -> class ConcreteClass is as deep as it goes), and a concrete class never extends another concrete class. (In the actual implementation, method returns a Play Framework JsObject instead of a Map[String, Any].) We're trying to replace this with a type class:

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

I'm running into two problems:

A. Mimicking the super.method ++ functionality from the previous implementation. At present I'm using

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) ++  ... 
  }
}

but this is ugly, and I won't get a warning or error if I accidentally call a method too far up the hierarchy e.g. if Clazz2 calls superClassMethod instead of subSuperClassMethod.

B. Calling method on a superclass, e.g.

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

Ideally I'd like to be able to tell the compiler that every subclass of SuperClass has a corresponding implicit object for method in the type class and so s.method is type-safe (or I'll get a compile time error if I've neglected to implement a corresponding implicit object for a subclass of SuperClass), but instead all I've been able to come up with is

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
      ...
    }
  }
}

which is ugly and won't give me a compile-time warning or error if I've omitted a class since I can't define SuperClass as a sealed trait.


We'd be open to alternatives to type classes that would allow us to concentrate the method code in one place. method is only being called from two places:

A. Other method implementations, for example Clazz1 has a val clazz2: Option[Clazz2], in which case the method implementation in Clazz1 would be something like

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

B. The top level Play Framework controller (i.e. the abstract class from which all of the controllers inherit), where we've defined a three ActionBuilders that call method, e.g.

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

  MethodActionBuilder.apply(f)
}

解决方案

I think type classes are not compatible with your scenario. They are useful when the types are disjoint, but you actually require that the instances are reflecting a super-type/sub-type hierarchy and are not independent.

With this refactoring, you are just creating the danger of the wrong instance being picked:

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!

So you ended up re-writing the polymorphic dispatch with your pattern matcher in SuperClassMethod. You basically go OO -> FP -> OO, while rendering the idea of type classes unusable (to be open), ending up rather in a sum type (all sub types known).

这篇关于多态性与Scala类型的类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆