Примесь черт с конфликтующими членами: почему мой код может быть успешно скомпилирован? - PullRequest
4 голосов
/ 03 августа 2020

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

Недавно я изучаю систему черт Scala.

Я провел несколько экспериментов с конфликтующими элементами, см. Следующий код:

trait TA {
  def play() = println("TA play")
}

trait TB {
  def play() = println("TB play")
}

trait TC {
  def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
}

Конечно, компиляция этого кода завершилась неудачно, как и ожидалось:

Error:(13, 8) class MyClass inherits conflicting members:
  method play in trait TB of type ()Unit  and
  method play in trait TC of type ()Unit
(Note: this can be resolved by declaring an override in class MyClass.)
class MyClass extends TA with TB with TC {
      ^

Насколько я понимаю,

Линеаризация класса MyClass равна {MyClass, TC, TB, TA}, но поскольку нет override на play из TB и play из TC, скомпилировать его не удалось.

Одним из способов обхода проблемы является отметка override на TA, TB, TC, как показано ниже:

trait IPlay {
  def play()
}

trait TA extends IPlay {
  override def play() = println("TA play")
}

trait TB extends IPlay {
  override def play() = println("TB play")
}

trait TC extends IPlay {
  override def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
}

Хорошо, этот код может быть успешно скомпилирован, как и ожидалось.

Но как насчет этого:

trait TA {
  def play() = println("TA play")
}

trait TB {
  def play() = println("TB play")
}

trait TC {
  def play() = println("TC play")
}

class MyClass extends TA with TB with TC {
  override def play(): Unit = {
    println("MyClass play")
    super.play()
    super[TC].play()
    super[TA].play()
    super[TB].play()
  }
}

(new MyClass).play()

// -- Output:
// MyClass play
//   TC play
//   TC play
//   TA play
//   TB play

Что меня удивило, так это то, что этот код также может быть успешно скомпилирован.

Линеаризация класса MyClass по-прежнему будет {MyClass, TC, TB, TA} и нет override в play из TB и play из TC, единственная разница в том, что я добавил override на play в MyClass.

Почему он может быть успешно скомпилирован?

Обратите внимание, что Scala не Java или C#, по умолчанию нет поведение конфликтующих элементов.

В Java, если вы не помечаете метод как @Override, поведение по умолчанию имеет приоритет.

В C#, если вы не ' t пометить метод как override, поведение по умолчанию скрывается.

В Scala, если вы не помечаете метод как override, поведения по умолчанию нет, они должны быть конфликтующими членами , IMO.

Следовательно, я думаю, что компиляция приведенного выше кода должна быть неудачной, но почему она может быть успешной?

Или мое понимание неверно.

Большое спасибо.

Ответы [ 2 ]

4 голосов
/ 04 августа 2020

Чтобы понять, откуда берутся потенциальные конфликты, нам нужно принять «точку зрения» MyClass.

Давайте рассмотрим ваши примеры, чтобы понять, почему вы получаете такие результаты (для упрощения я используйте 2 черты вместо 3).

Первый пример: обычный конфликт

trait TA { def play() = println("TA") }
trait TB { def play() = println("TB") }
class MyClass extends TA with TB {}

Это не удается, потому что обе черты определяют метод play с одинаковой сигнатурой, но неясно, какой один на выбор для MyClass.play. Можно было бы возразить, что «компилятор мог выбрать TB.play, потому что это последний смешанный признак», но поскольку не было объявлено намерение переопределения чего-либо, Scala отклоняет его. В принципе, это могло бы сработать, но команда Scala решила заставить вас express более четко выразить ваши намерения.

Как это сделать?

Ясные намерения 1: переопределение в трейте

trait T { def play(): Unit }
trait TA { override def play() = println("TA") }
trait TB { override def play() = println("TB") }
class MyClass extends TA with TB {}

Второй пример показывает один способ выразить намерение переопределения. Поскольку TA.play и TB.play объявлены с override, ясно, что они намереваются переопределить метод play(): Unit, который находится в области видимости. Вот почему компилятор позволяет TB.play «заменить» TA.play в MyClass.

Четкие намерения 2: переопределить в классе

class MyClass extends TA with TB {
  override def play(): Unit = {
    super.play() // super[TB].play()
    super[TA].play()
  }
}

Переопределение метода в классе - другое способ решения проблемы. Здесь мы точно знаем, что такое MyClass.play, поскольку он явно определен. С точки зрения MyClass двусмысленности нет.

Компилятор подходит для super[TA].play(), потому что ясно, какой метод вызывается: тот, который определен в trait TA как def play() = println("TA"). Итак, super[TA].play() выполнит println("TA").

super.play() немного менее явный, но правила композиции классов ясны: super в этом контексте относится к последнему миксину, т.е. как super[TB].

1 голос
/ 04 августа 2020

Вы вручную определили поведение в подклассе. Почему вы удивлены? Что неверно в этом случае?

Я думаю, что основная запутывающая вещь в этом примере заключается в том, что «super.play ()» является законным и относится к последнему унаследованному члену - T C. Я думаю, что это просто обходной путь, когда мы просто берем последнее. Я полагаю, они могли бы решить первый случай (который не компилируется) этим обходным путем - просто возьмите последнюю унаследованную реализацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...