¿Debería compilarse lo siguiente sin necesidad de una definición de tipo explícita en this
?inferir un supertipo común basado en un valor de parámetro y tipos de parámetro de función
def prepList[B >: A](prefix: PlayList[B]) : PlayList[B] =
prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))
Me parece que el tipo debería ser capaz de inferir. ¿Es esto solo una limitación en el compilador de Scala, o hay una razón teórica de tipo que esto no se puede hacer? Realmente no tengo una idea de lo que se puede esperar que maneje el invocador de tipo Scala.
Trabajando a través de ese método:
B >: A
por definiciónthis
ha escribaPlayList[A]
, que es un subtipo dePlayList[B]
desdeB >: A
y la Lista de Reproducción es covariante enA
.node
tiene tipoB
, el tipo de parámetro deprefix
.- segundo parámetro para el parámetro de función
f
enfoldr
tiene el mismo tipo (declaradoB
) como el primer parámetro parafoldr
. - Así
suffix
tiene el mismo tipo quethis
, por lo que en particular es unPlayList[A]
. DesdeB >: A
,suffix.prepNode()
toma unB
.
Me gustaría que el compilador para ver que suffix.prepNode(node)
es legal donde node
tiene tipo B
. Parece ser capaz de hacer esto solo si especifico explícitamente un tipo en la invocación de foldr
o en la referencia al this
en esa invocación.
Curiosamente, si puedo especificar tipos explícitos sobre los parámetros de la función como (node: B, suffix: PlayList[B])
, un error de coincidencia de tipos está siendo generada en el parámetro de la llamada al método suffix.prepNode(node)
: "found: B, required: A"
estoy usando Scala 2.8 RC6. Ejemplo completo a continuación, la línea en cuestión es la línea 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))
}
EDIT: segundo intento de razonar a través de la compilación de los pasos
- Cambio de nombre para mayor claridad,
foldr
tome parámetros de tipos(T)((U, T) => T)
. Estamos intentando inferir los valores de los tiposU
yT
. - Existe una relación entre el primer parámetro para
foldr
y el segundo parámetro para la función: son lo mismo,T
. (En respuesta parcial a Daniel.) - Los tipos de los objetos que estamos pasando en que esos parámetros son
this: PlayList[A]
ysuffix: PlayList[B]
- Así, desde
B >: A
, el tipo súper común más específica estáPlayList[B]
; por lo tanto, tenemosT == PlayList[B]
.Nota que no necesitamos ninguna relación entreU
yT
para deducir esto.
Esto es donde se queda bloqueado:
- Desde el mensaje de error de compilación, el inferencer piensa claramente que
node
tiene tipoB
(es decir,U == B
). - No puedo ver cómo llega a la conclusión de que
U == B
sin deducirlo del parámetro de tiposuffix
. (¿Puede el compilador scala hacer esto?) - Si ese paso de inferencia es lo que sucede, entonces se deduce que
U == B
, y hemos compilado con éxito. Entonces, ¿qué paso salió mal?
EDIT 2: En cambio de nombre de los tipos de parámetros por encima de foldr
echaba de menos que U == A
por definición, es el parámetro de tipo de la clase PlayList
. Sin embargo, creo que esto sigue siendo coherente con los pasos anteriores, ya que lo estamos llamando en una instancia de PlayList[B]
.
Por lo tanto, en el sitio de llamada, T == PlayList[B]
como el super-tipo menos común de un par de cosas, y U == B
por definición de foldr
en el receptor. Eso parece lo suficientemente concisa para reducir a un par de opciones:
- el compilador no puede resolver los múltiples tipos y calcular el límite superior del
B
- error en llegar de tipo de retorno
PlayList[B]
defoldr
al tipo de parámetro deprepNode
(escéptico)
Tienes razón en que estoy buscando el compilador para ver que estos deberían ser los mismos, en cierto sentido, para esta invocación, pero en general T y U no son lo mismo: considera el caso en el que los llamo dos métodos por separado. B en esa declaración es solo el nombre del tipo obligado usado dentro de ese alcance de método. En este caso, tengo entendido que PlayList [B] es un supertipo común de este y sufijo, por lo que debe inferirse que es el tipo de ambos. Es decir, el parámetro de tipo formal B de prepNode debe tener el mismo valor en esta invocación que el parámetro de tipo real B de prepList. –
Entonces puede ser que mi sugerencia no sea la correcta. Aún así, parece que tener que especificar 'this: PlayList [B]' tiene sentido para mí. – huynhjl