Реализуйте методы черты, используя типы подклассов - PullRequest
3 голосов
/ 11 июля 2019

Я хочу, чтобы черта Foo предоставляла метод transform, который бы применял к нему функцию.Также я хочу, чтобы у реализующих классов был метод increment, который также каким-то образом трансформировал бы объект.Наивное решение:

trait Foo {
  def transform(fun: Foo => Foo): Foo = fun(this)
  def increment(n: Int): Foo
}

case class A(a: Int) extends Foo {
  // expecting available: transform(fun: A => A): A
  // to be implemented: increment(n: Int): A
  ...
}

Выше не будет работать ... Наследуется transform все еще ожидает Foo => Foo, а не A => A и increment все еще хочет вернуть Foo, а не A.

Еще одна попытка:

trait Foo {
  def transform[C <: Foo](fun: C => C): C = fun(this.asInstanceOf[C])
  def increment[C <: Foo](n: Int): C
}

case class A(a: Int) extends Foo {
  def increment(n: Int) = A(a + n)
}

A не скомпилируется - он все равно будет жаловаться на сигнатуру.

Извлекая функцию increment, преобразуйтеработает.Однако asInstanceOf выглядит немного небезопасно.Кроме того, мне нужно явно предоставить параметр типа для transform:

val a = A(1)
a.transform[A](x => x.copy(x.a + 1)) // returns A(2)

Интересно, есть ли разумный способ сделать это.

1 Ответ

5 голосов
/ 12 июля 2019

Самый прямой способ получить то, что вы хотите, - это переместить параметр типа в объявление признака. Это дает trait Foo[C]{...}. Однако использование copy в вашем transform по-прежнему не сработает, поскольку черта Foo ничего не знает о том, что ее расширяет. Вы можете дать ему немного больше информации, набрав self typing :

trait Foo[C] {
  this: C =>
    def transform(fun: C => C): C = fun(this)
    def increment(n: Int): C
}

case class A(a: Int) extends Foo[A] {
  def increment(n: Int) = A(a + n)
}

A extends Foo[A] немного неудобно использовать здесь, но он работает, поскольку теперь, когда вы расширяете Foo, он предоставляет информацию этого типа обратно в черту. Это все еще немного неловко, хотя. Оказывается, существует методика, называемая классы типов , которую мы можем использовать здесь, чтобы потенциально улучшить ситуацию. Сначала вы настраиваете свою черту. В классе типов существует только одна реализация признака для каждого типа, поэтому каждый метод должен также использовать тот экземпляр, с которым вы хотите работать:

trait Foo[C] {
  def transform(c: C)(f: C => C): C
  def increment(c: C, inc: Int): C
}

Далее в объекте-компаньоне вы устанавливаете экземпляры класса типов для типов, которые вам нужны:

case class A(a: Int)

object Foo {
  implicit val ATransform = new Foo[A] {
    def transform (base: A)(f: A => A) = f(base)
    def increment(base: A, inc: Int) = A(base.a+inc)
  }

  //Convenience function for finding the instance for a type.
  //With this, Foo[A] is equivalent to implicitly[Foo[A]]
  def apply[C](implicit foo: Foo[C]) = foo
}

Теперь мы можем использовать класс типов следующим образом:

val b = A(3)
Foo[A].transform(b)(x=>x.copy(a=x.a+1)) //A(4)
Foo[A].increment(b,5) //A(8)
...