Должно ли следующее компилироваться без необходимости явного определения типа в this
?
def prepList[B >: A](prefix: PlayList[B]) : PlayList[B] =
prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))
Мне кажется, что тип должен быть в состоянии вывести. Это просто ограничение в компиляторе Scala, или есть теоретико-типовая причина, по которой это невозможно? У меня пока нет ощущения того, что можно ожидать от логического вывода типа Scala.
Работа через этот метод:
B >: A
по определению
this
имеет тип PlayList[A]
, который является подтипом PlayList[B]
, поскольку B >: A
и PlayList ковариантны в A
.
node
имеет тип B
, тип параметра prefix
.
- второй параметр к параметру функции
f
в foldr
имеет тот же тип (объявлен B
), что и первый параметр для foldr
.
- Таким образом,
suffix
имеет тот же тип, что и this
, поэтому, в частности, это PlayList[A]
. Так как B >: A
, suffix.prepNode()
занимает B
.
Я бы хотел, чтобы компилятор увидел, что suffix.prepNode(node)
допустимо, когда node
имеет тип B
. Похоже, что это возможно сделать, только если я явно укажу тип в вызове foldr
или в ссылке на this
в этом вызове.
Интересно, что если я указываю явные типы в параметрах функции как (node: B, suffix: PlayList[B])
, ошибка параметра несоответствия типов все еще генерируется в параметре для вызова метода suffix.prepNode(node)
: "found: B, required: A"
Я использую Scala 2.8 RC6. Полный пример ниже, рассматриваемая строка - строка 8.
sealed abstract class PlayList[+A] {
import PlayList._
def foldr[B](b: B)(f: (A, B) => B): B
def prepNode[B >: A](b: B): PlayList[B] = nel(b, this)
def prepList[B >: A](prefix: PlayList[B]): PlayList[B] =
// need to specify type here explicitly
prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))
override def toString = foldr("")((node, string) => node + "::" + string)
}
object PlayList {
def nil[A]: PlayList[A] = Nil
def nel[A](head: A, tail: PlayList[A]): PlayList[A] = Nel(head, tail)
def nel[A](as: A*): PlayList[A] = as.foldRight(nil[A])((a, l) => l.prepNode(a))
}
case object Nil extends PlayList[Nothing] {
def foldr[B](b: B)(f: (Nothing, B) => B) = b
}
case class Nel[+A](head: A, tail: PlayList[A]) extends PlayList[A] {
def foldr[B](b: B)(f: (A, B) => B) = f(head, tail.foldr(b)(f))
}
РЕДАКТИРОВАТЬ: вторая попытка рассуждать через этапы компиляции
- Переименование для наглядности,
foldr
принимает параметры типов (T)((U, T) => T)
. Мы пытаемся вывести значения типов U
и T
.
- Существует связь между первым параметром
foldr
и вторым параметром функции - это одно и то же, T
. (В частичном ответе Даниилу.)
- Типы объектов, которые мы передаем в качестве этих параметров:
this: PlayList[A]
и suffix: PlayList[B]
- Итак, начиная с
B >: A
, наиболее специфический общий супер тип - PlayList[B]
; следовательно, у нас есть T == PlayList[B]
. Обратите внимание , что нам не нужны никакие отношения между U
и T
, чтобы сделать это.
Вот где я застрял:
- Из сообщения об ошибке компиляции выводчик ясно считает, что
node
имеет тип B
(то есть U == B
).
- Я не вижу, как можно прийти к выводу, что
U == B
, не выводя его из параметра типа suffix
. (Может ли компилятор Scala сделать это?)
- Если на этом этапе логического вывода происходит то, что следует,
U == B
, и мы успешно скомпилировали. Так какой шаг пошёл не так?
РЕДАКТИРОВАТЬ 2: При переименовании типов параметров foldr
выше я пропустил, что U == A
по определению, это параметр типа класса PlayList
. Я думаю, что это все еще согласуется с вышеупомянутыми шагами, хотя, так как мы вызываем его для экземпляра PlayList[B]
.
Таким образом, на сайте вызовов T == PlayList[B]
является наименее распространенным супертипом пары вещей, а U == B
по определению foldr
на приемнике. Это кажется достаточно кратким, чтобы ограничиться парой вариантов:
- компилятор не может разрешить эти несколько типов и вычисляет верхнюю границу
B
- ошибка при получении типа возврата
PlayList[B]
из foldr
к типу параметра prepNode
(скептически)