2011-10-20 17 views
24

Cuando puedo compilar:"Tipo de parámetro en el refinamiento estructural no puede referirse a un tipo abstracto definida fuera de ese refinamiento"

object Test extends App { 
    implicit def pimp[V](xs: Seq[V]) = new { 
    def dummy(x: V) = x 
    } 
}                                                    

me sale:

$ fsc -d aoeu go.scala 
go.scala:3: error: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement 
    def dummy(x: V) = x 
     ^
one error found 

¿Por qué?

(Scala: "Parameter type in structural refinement may not refer to an abstract type defined outside that refinement" en realidad no responder a esta.)

Respuesta

20

No está permitido por la especificación. Consulte 3.2.7 Tipos compuestos.

Dentro de una declaración de método en un refinamiento estructural, el tipo de cualquier parámetro de valor solo puede referirse a los parámetros de tipo o tipos abstractos que están contenidos dentro del refinamiento. Es decir, debe referirse a un parámetro de tipo del método en sí mismo, oa una definición de tipo dentro del refinado. Esta restricción no aplica al tipo de resultado de la función.

Antes de que Bug 1906 se haya solucionado, el compilador habría compilado esto y habría obtenido un método no encontrado en tiempo de ejecución. Esto se arregló en revision 19442 y es por eso que recibes este maravilloso mensaje.

La pregunta es, entonces, ¿por qué no está permitido?

Aquí está muy detailed explanation de Gilles Dubochet de la lista de correo de scala en 2007. Se reduce a que los tipos estructurales usan la reflexión y el compilador no sabe cómo buscar el método para llamar si usa un tipo definido fuera del refinamiento (el compilador no sabe de antemano cómo llenar el segundo parámetro de getMethod en p.getClass.getMethod("pimp", Array(?))

Pero ir a buscar en el segundo palo, responderá a su pregunta y poco más.

Editar:

Hola lista.

Intento definir tipos estructurales con el tipo de datos abstracto en el parámetro de función . ... ¿Cualquier razón?

he oído hablar de dos cuestiones relativas a la tipificación estructural extensión de la Scala 2,6 últimamente, y me gustaría responder a ellos aquí.

  1. ¿Por qué cambiamos los valores nativos de Scala (“int”, etc.) esquema de boxeo a (“java.lang.Integer”) de Java.
  2. Por qué es necesaria la restricción de los parámetros para los métodos estructuralmente definidos ("El tipo de parámetro en refinamiento estructural no se refiere a al tipo abstracto definido fuera de ese mismo refinamiento").

Antes de que pueda responder a estas dos preguntas, necesito hablar sobre la implementación de los tipos estructurales.

El sistema de tipos de la JVM es muy básico (y corresponde a Java 1.4). Eso significa que muchos tipos que se pueden representar en Scala no pueden ser representados en la máquina virtual. Los tipos dependientes de ruta ("x.y.A"), los tipos simples ("a.type"), los tipos compuestos ("A con B") o los tipos abstractos son todos los tipos que no se pueden representar en el sistema de tipos de la JVM.

Para poder compilar a JVM bytecode, los compiladores Scala cambia los tipos Scala del programa a su “borrado” (véase la sección 3.6 de la referencia ). Los tipos borrados se pueden representar en el sistema de tipos de la VM y definen una disciplina de tipo en el programa que es equivalente a la de el programa escrito con tipos de Scala (guardando algunas versiones), aunque menos preciso. Como nota al margen, el hecho de que los tipos se borren en la VM explica por qué las operaciones en la representación dinámica de tipos (patrón haciendo coincidir en tipos) son muy restringidas con respecto al sistema tipo de Scala.

Hasta ahora, todo tipo de construcciones en Scala se podía borrar de alguna manera. Esto no es cierto para tipos estructurales. El tipo estructural simple "{def x: Int}" no se puede borrar a "Objeto" ya que la VM no permitiría acceder al campo "x". Usar una interfaz "interfaz X {int x {}; } " ya que el tipo borrado no funcionará porque cualquier instancia enlazada por un valor de de este tipo tendría que implementar esa interfaz que no se puede hacerse en presencia de una compilación separada. De hecho (tengan paciencia conmigo) cualquier clase que contenga un miembro del mismo nombre que un miembro definido en un tipo de estructura en cualquier lugar tendría que implementar la interfaz correspondiente . Desafortunadamente, esta clase puede definirse incluso antes de que se conozca la existencia del tipo estructural .

En su lugar, cualquier referencia a un miembro estructuralmente definido se implementa como una llamada reflexiva, omitiendo por completo el sistema de tipos de la VM. Por ejemplo def f(p: { def x(q: Int): Int }) = p.x(4) será reescrito a algo como:

def f(p: Object) = p.getClass.getMethod("x", Array(Int)).invoke(p, Array(4)) 

Y ahora las respuestas.

  1. “Invoke” utilizará en caja valores (“java.lang.Integer”) cada vez que el método invocado utiliza valores nativos (“int”). Eso significa que la llamada anterior debe parecerse realmente a "... invoke (p, Array (new java.lang.Integer (4))). IntValue".

Los valores enteros en un programa de Scala ya están a menudo en cajas (para permitir que el tipo “any” ) y sería un desperdicio de desempacar desde propio esquema de boxeo de Scala a Rebox inmediatamente como java.lang.Integer .

Peor aún, cuando una llamada reflexiva tiene el tipo de devolución "Cualquiera", ¿qué se debe hacer cuando se devuelve un java.lang.Integer? El método llamado puede devolver un "int" (en cuyo caso debe estar sin casillas y volver a estar como un recuadro de Scala) o puede devolver un java.lang.Integer que no debe tocarse.

En cambio, decidimos cambiar el propio esquema de boxeo de Scala a Java. El dos problemas anteriores simplemente desaparecen. Algunas optimizaciones de relacionadas con el rendimiento que tuvimos con el esquema de boxeo de Scala (precalcular la forma en caja de los números más comunes) fueron fáciles de usar con el boxeo Java también. Al final, usar Java boxing fue incluso un poco más rápido que nuestro propio esquema.

  1. segundo parámetro “getMethod” 's es una matriz con los tipos de los parámetros del método (estructuralmente definida) para buscar - para seleccionar qué método para obtener cuando el nombre está sobrecargado Este es el , un lugar donde se necesitan tipos exactos y estáticos en el proceso de que traduce una llamada de miembro estructural. Por lo general, los tipos estáticos explotables para un parámetro de método se proporcionan con la definición de tipo estructural . En el ejemplo anterior, el tipo de parámetro de "x" es conocido por como "Int", lo que permite buscarlo.

tipos de parámetros definidos como tipos abstractos donde el tipo abstracto es definido dentro del alcance de la refinación estructural no son un problema ya sea: def f (p: {def x [T] (t: T): Int}) = p.xInt En este ejemplo, sabemos que cualquier instancia pasada a "f" como "p" definirá "x [T] (t: T)" que necesariamente se borrará a "x (t: Object) ) ". La búsqueda se realiza correctamente en el tipo borrado: def f (p: Object) = p.getClass.getMethod ("x", Array (Object)). Invoke (p, Array (nuevo java.lang.Integer) (4)))

Pero si un tipo abstracto desde fuera alcance del refinamiento estructural se utiliza para definir un parámetro de un método estructural, todo se rompe: def f [T] (p: {def x (t: T): Int}, t: T) = px (t) Cuando se invoca "f", se puede instanciar "T" a cualquier tipo, por ejemplo: f [Int] ({def x (t: Int) = t}, 4) f [Cualquiera] ({def x (t: Any) = 5}, 4) La búsqueda del primer caso tendría que ser "getMethod (" x ", Array (int)) "y para el segundo" getMethod ("x", Array (Object)) ", y no hay manera de saber cuál generar en el cuerpo de " f ":" px (t) ".

Para permitir la definición de una única llamada “getMethod” dentro “f” 's cuerpo para cualquier instanciación de ‘T’ que requeriría cualquier objeto pasado a ‘f’ como el parámetro ‘p’ para tener el tipo de “ t "borrado a" Cualquiera ". Esta sería una transformación donde el tipo de miembros de una clase depende de cómo se usan las instancias de esta clase en el programa. Y esto es algo que definitivamente no queremos hacer (y no se puede hacer con compilación separada ).

Alternativamente, si Scala admitió los tipos de tiempo de ejecución uno podría usarlos para resolver este problema. Tal vez algun dia ...

Pero, por ahora, el uso de tipos abstractos para los parámetros del método estructural tipos está simplemente prohibido.

Atentamente, Gilles.

+0

¿Hay alguna manera de hacer que esto funcione? El compilador podría encontrar el tipo si lo intentara realmente duro. – Jus12

10

descubrió el problema poco después de la publicación de esta: tengo que definir una clase llamada en lugar de utilizar una clase anónima. (Aun así, me encantaría escuchar una mejor explicación del razonamiento).

object Test extends App { 
    case class G[V](xs: Seq[V]) { 
    def dummy(x: V) = x 
    } 
    implicit def pimp[V](xs: Seq[V]) = G(xs) 
}                                                    

funciona.

+0

Gracias por publicar esto, porque también solucionó mi problema. Y a partir de la solución, finalmente puedo realizar una ingeniería inversa a lo que se refería el error; la respuesta aceptada anteriormente no me ayudó con eso. – arya

Cuestiones relacionadas