2010-06-28 13 views
7

¿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ón
  • this ha escriba PlayList[A], que es un subtipo de PlayList[B] desde B >: A y la Lista de Reproducción es covariante en A.
  • node tiene tipo B, el tipo de parámetro de prefix.
  • segundo parámetro para el parámetro de función f en foldr tiene el mismo tipo (declarado B) como el primer parámetro para foldr.
  • Así suffix tiene el mismo tipo que this, por lo que en particular es un PlayList[A]. Desde B >: A, suffix.prepNode() toma un B.

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 tipos U y T.
  • 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] y suffix: PlayList[B]
  • Así, desde B >: A, el tipo súper común más específica está PlayList[B]; por lo tanto, tenemos T == PlayList[B].Nota que no necesitamos ninguna relación entre U y T para deducir esto.

Esto es donde se queda bloqueado:

  • Desde el mensaje de error de compilación, el inferencer piensa claramente que node tiene tipo B (es decir, U == B).
  • No puedo ver cómo llega a la conclusión de que U == B sin deducirlo del parámetro de tipo suffix. (¿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] de foldr al tipo de parámetro de prepNode (escéptico)

Respuesta

3

No soy un experto en tipo, pero esto es lo que sucede cuando trato de inferir.

((node, suffix) => suffix.prepNode(node)) devuelve un tipo desconocido PlayList[T], donde T extiende A. Se pasa como un argumento para foldr que devuelve el tipo de función que se le pasó (PlayList[T] donde T se extiende A). Y esto se supone que es de algún tipo PlayList[B].

Así que mi conjetura es que this:PlayList[B] es necesario para indicar que T y B están relacionados.

¿Puede ser necesario que PlayList sea paramétrico en dos tipos PlayList[+A, B >: A] ya que tiene prePNode y propList que parecen funcionar en el mismo tipo que extiende A?

Dicho de otra manera, su definición de clase original podría haber sido definido así:

def prepNode[T >: A](b: T): PlayList[T] 
def prepList[U >: A](prefix: PlayList[U]): PlayList[U] 

Pero que utilizó B en ambos casos, y el compilador no sabe que T y U son los mismos.


Edición, puede jugar con la opción -explaintypes y ver lo que el compilador dependiendo del tipo de consejos que se obtiene. Aquí está la salida de Explatypes y eliminando el :PlayList[B] (con 2.8.0.RC1):

$ scalac -explaintypes -d classes Infer.scala 
found : node.type (with underlying type B) 
required: A 
    prefix.foldr(this)((node, suffix) => suffix.prepNode(node)) 
                 ^
node.type <: A? 
    node.type <: Nothing? 
    B <: Nothing? 
     <notype> <: Nothing? 
     false 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    false 
    B <: A? 
    B <: Nothing? 
     <notype> <: Nothing? 
     false 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    Any <: A? 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    false 
false 

Espero que esto ayude a arrojar algo de luz. Puede ser una pregunta acerca de cuándo Scalac puede inferir y cuándo no puede inferir sería útil.

+0

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. –

+0

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

1

El problema es que foldr no especifica B >: A, por lo que, en lo que se refiere a foldr, no hay ninguna relación entre él es poseer A y B tipos. En lo que respecta a foldr, suffix y node no guardan relación alguna, aunque usted haya pasado parámetros relacionados.

+0

No estoy seguro de seguir. En general, no queremos esa restricción en la declaración de foldr (ciertamente hay otros usos válidos donde B no tiene relación con A) y no veo que lo necesitemos en este caso para resolver los tipos. He actualizado la pregunta con un intento de razonar esto a través de ... –

Cuestiones relacionadas