Полиморфизм с классами типов Scala

Мы реорганизуем унаследованный 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.

Б. Вызов 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], и в этом случае реализация method в Clazz1 будет чем-то вроде

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

person Zim-Zam O'Pootertoot    schedule 05.12.2013    source источник


Ответы (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, при этом делая идею непригодных для использования классов типов (чтобы быть открытыми), заканчиваясь скорее типом суммы (все подтипы известны).

person 0__    schedule 06.12.2013

@ 0__ указывает на что-то - неявное разрешение происходит при компиляции, поэтому экземпляр класса типа, который используется для данного ввода, не зависит от типа среды выполнения этого ввода.

Чтобы получить желаемое поведение, вам нужно написать некоторое неявное определение, которое будет отражать фактический тип объекта, для которого вы хотите вызвать method, чтобы выбрать правильный экземпляр класса типов.

Я думаю, что это скорее проблема с обслуживанием, чем то, что у вас есть сейчас.

person ellisbben    schedule 10.12.2013

Проще говоря: если вы хотите разместить свою реализацию в одном месте, вы должны использовать классы case для своей иерархии:

abstract class SuperClass;

case class Clazz(...) extends SuperClass;

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

(Обратите внимание, что метод, конечно, может быть рекурсивным). Поскольку в scala можно использовать открытый тип суммы (компилятор не будет предупреждать об отсутствующих шаблонах), это должно решить вашу проблему, не злоупотребляя классами типов. .

person choeger    schedule 14.12.2013