Дисперсия и переопределение функций Scala - PullRequest
5 голосов
/ 28 ноября 2010

У меня небольшая проблема с пониманием дисперсии методов при перегрузке.

Хотя это прекрасно работает из-за ковариации в типе возврата

class Bla 
class Fasel extends Bla 

trait Test[A] {
 def tester(): Bla = new Bla
}

class FooTest[A](a: A) extends Test[A] {
 override def tester(): Fasel = new Fasel                                      
}

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

class Bla 
class Fasel extends Bla 

trait Test[A] {
 def tester(a: Fasel): Bla = new Bla                                           
}

class FooTest[A](a: A) extends Test[A] {
 override def tester(a: Bla): Fasel = new Fasel
}

Что я здесь не так делаю?Любые указатели?

С уважением, raichoo

Ответы [ 4 ]

11 голосов
/ 28 ноября 2010

Здесь происходит две вещи:

  1. Функция и метод не одно и то же
  2. Методы не являются полиморфными по типам своих параметров

Ваш tester метод - это метод, не Function1. Его можно поднять в функцию, используя синтаксис подчеркивания:

val f = (new FooTest[String]).tester _ // Fasel => Bla

Эта функция будет противоречивой в своем типе ввода. (Однако стоит сказать, что функции не могут быть параметризованы , а также стоит сказать, что мне нужно было иметь экземпляр Foo или FooTest, чтобы получить объект функции для метода tester. . Это, конечно, следует из первого наблюдения!)

Функция - это объект, ее нельзя переопределить , так как это не имеет смысла. Методы могут быть переопределены. Однако, как я уже сказал выше, переопределение не является полиморфным в типах параметров метода. Так, например:

class A {
  def foo(a : Any) = println("A: " + a)
}

class B extends A {
  override def foo(s : String) = println("B " + s) //will not compile!
}

Два метода в моем примере выше - это два отдельных метода: динамическая диспетчеризация работает только с целью метода (т. Е. Объектом, для которого он вызывается).

В приведенном выше примере, если вы удалите объявление override, код скомпилируется. Если вы запустите следующее:

(new B).foo(1)   //prints A 1
(new B).foo("s") //prints B s

Это потому, что хотя оба метода называются foo, они совершенно разные методы (т. Е. У меня перегружено foo, а не переопределено it). Лучше всего понимать, что аргументы метода (включая их типы) образуют часть уникального имени этого метода . Один метод переопределяет другой, только если они имеют точно такое же имя .

<Ч />

По сути, вы перепутали две отдельные и не связанные вещи в вашем вопросе, которые я поясню для ясности:

  • Примечания к дисперсии Function1 определяют, что означает, что одна функция является подтипом другой (и, следовательно, присваивается для ссылки на данный тип).
  • Методы могут быть переопределены на подклассах, а в спецификации языка изложены правила, когда такое переопределение имеет место.
4 голосов
/ 28 ноября 2010

Соответствующие фрагменты спецификации:

Типы методов

Тип метода внутренне обозначается как (Ps)U, где (Ps) являетсяпоследовательность имен параметров и типов (p1 :T1,...,pn :Tn) для некоторых n≥0 и U является типом (значение или метод).Этот тип представляет именованные методы, которые принимают аргументы с именем p1, ..., pn типов T1,...,Tn и возвращают результат типа U.

Типы методов не существуют как типы значений.Если имя метода используется в качестве значения, его тип неявно преобразуется в соответствующий тип функции (§6.26).

Переопределение

Член M класса C, который соответствует (§5.1.3) не приватный член M′ базового класса C, как говорят, переопределяет этого члена.В этом случае привязка переопределяющего элемента M должна включать (§3.5.2) привязку переопределенного элемента M′.

Соответствие

Если Ti ≡ Ti′ для i = 1, ..., n и U соответствует U′, то тип метода (p1 : T1,...,pn :Tn)U соответствует (p1′ :T1′,...,pn′ :Tn′)U′.

Подразделения

Объявление или определение в некотором составном типе типа класса C включает другое объявление с тем же именем в некотором составном типе или типе класса C′, если одинимеет место следующее:

  • Объявление значения или определение, которое определяет имя x с типом T, включает в себя объявление значения или метода, которое определяет x с типом T′, при условии T <: T′.
0 голосов
/ 28 ноября 2010

Вы можете переопределить и изменить тип возвращаемого значения на подтип, но хотя принятие супертипа для аргумента будет соответствовать принципу подстановки, это недопустимо (это так же, как в java). Причина в том, что вы также можете перегружать методы (несколькометоды с одинаковым именем, количеством и типом аргументов), и ваш метод будет рассматриваться как перегрузка.Я предполагаю, что это в основном вопрос совместимости JVM и наличия разумной спецификации.Перегрузка уже делает спецификацию scala довольно сложной.Простая маршрутизация переопределенного метода на перегруженный с измененной сигнатурой может быть достаточно хорошей:

class FooTest[A] extends Test[A] {
   override def test(a: Fasel) : Fasel = test(a.asInstanceOf[Bla])
   def test(a: Bla) : Fasel = new Fasel
}

То, что вы можете сделать, это сделать параметр типа контравариантным, при условии, что он появляется только в противоположной позиции (упрощение выглядит кактип аргумента, а не тип результата), но он совсем другой:

trait Test[-A] {
  // note the - before A. 
  // You might want to constraint with -A >: Fasel
  def tester(a: A) : Bla = new Bla
}

class FooTest extends Test[Bla] {
  override def tester(a: Bla): Fasel = new Fasel
}

val testOfBla: Test[Bla] = new FooTest
val testOfFasel: Test[Fasel] = testOfBla 
  // you can assign a Test[Bla] to a test[Fasel] because of the -A
0 голосов
/ 28 ноября 2010

Хорошо, во втором примере подпись tester() в Test объявляет аргумент Fasel, но с переопределенной подписью FooTest tester() объявляется с Bla в качестве аргумента. Поскольку Fasel является подтипом Bla по иерархии extend, это, вероятно, неправильно.

...