2012-03-07 16 views
5

Dada una tupla con elementos de tipo A y otro tipo parametrizado en A:tipo extraño desajuste cuando se utiliza el acceso de miembros en lugar de extractor de

trait Writer[-A] { def write(a: A): Unit } 
case class Write[A](value: A, writer: Writer[A]) 

y un sitio de uso:

trait Cache { def store[A](value: A, writer: Writer[A]): Unit } 

¿Por qué la siguiendo el trabajo como se esperaba, usando el extractor de la tupla:

def test1(set: Set[Write[_]], cache: Cache): Unit = 
    set.foreach { 
    case Write(value, writer) => cache.store(value, writer) 
    } 

Pero la siguiente falla:

def test2(set: Set[Write[_]], cache: Cache): Unit = 
    set.foreach { write => 
    cache.store(write.value, write.writer) 
    } 

con el mensaje de error

found : Writer[_$1] where type _$1 
required: Writer[Any] 
      cache.store(write.value, write.writer) 
             ^

¿Puedo fijar la segunda forma (test2) para compilar correctamente?

EDITAR

Partiendo de las ideas de Owen que probé si puedo hacer que funcione sin la coincidencia de patrones en absoluto (que es lo que quería en primer lugar). Aquí hay dos casos más extraños, uno de trabajo, el otro no:

// does not work 
def test3(set: Set[Write[_]], cache: Cache): Unit = { 
    def process[A](write: Write[A]): Unit = 
    cache.store(write.value, write.writer) 

    set.foreach(process) 
} 

// _does work_ 
def test4(set: Set[Write[_]], cache: Cache): Unit = { 
    def process[A](write: Write[A]): Unit = 
    cache.store(write.value, write.writer) 

    set.foreach(w => process(w)) 
} 

Sigue siendo bastante oscuro para mí ...

Respuesta

7

Correr con -Xprint:typer está iluminando aquí. El problema con test2 es que hay un tipo existencial, que aparece en dos lugares diferentes: tanto write.value y write.writer tienen un tipo existencial, pero, sobre todo, la compilador no tiene forma de saber que tienen la misma existencialmente variable de tipo cuantificado. Aunque acceda a ellos desde el mismo objeto, el compilador olvida que provienen del mismo lugar.

Cuando test1 está totalmente con tipo, ver:

def test1(set: Set[Write[_]], cache: Cache) = 
    set.foreach(((x0$1: Write[_]) => x0$1 match { 
     case (value: _$1, writer: Writer[_$1])Write[_$1]((value @ _), (writer @ _)) => 
      cache.store[_$1](value, writer) 
    })); 

El tipo de variable _$1 se corresponde con los valores. Coincidir con la variable de tipo _$1 lo vincula en el alcance del case, por lo que ya no es existencial, y Scala puede indicar que value y writer tienen el mismo parámetro de tipo.

La solución para test2 es no utilizar los existenciales:

def test2[A](set: Set[ Write[ A ]], cache: Cache) { 
    set.foreach { write => 
     cache.store(write.value, write.writer) 
    } 
} 

o para obligar a la variable de tipo con un fósforo:

def test2(set: Set[ Write[ _ ]], cache: Cache) { 
    set.foreach { case write: Write[a] => 
     cache.store(write.value, write.writer) 
    } 
} 

edición

Let Me esfuerzo para responder las nuevas preguntas que planteaste

La razón test3 no funciona, es que cuando se escribe:

set.foreach (proceso)

process, que es polimórfico, que se ha hecho monomórfica, por dos razones (que yo sepa de):

  1. Las funciones en Scala no pueden ser polimórficas; solo los metodos pueden ser process como se define como un método; cuando se usa como una función de primera clase, es una función.

  2. La forma en que el compilador realiza la inferencia es principalmente tomando valores polimórficos y uniéndolos para hacerlos menos polimórficos. Pasar un valor polimórfico real como argumento de método requeriría tipos de rango superior.

La razón de que test4hace trabajo es que la función literal:

set.foreach(w => process(w)) 

no es realmente una función polimórfica! Toma como argumento un tipo extenuamente calificado; pero no un tipo polimórfico Luego llama al método (no la función) process, y hace coincidir la variable de tipo existencial con el parámetro de tipo process. Muy salvaje, ¿eh?

También podría escribir:

set.foreach(process(_)) 

el cual, la creación de una función anónima, significa la misma cosa.

Otra ruta que puede o no puede encontrar adecuada sería de descartar tipos existenciales y miembros de tipo de uso:

trait Writable { 
    type A 
    val value: A 
    val writer: Writer[A] 
} 

case class Write[T](value: T, writer: Writer[ T ]) extends Writable { 
    type A = T 
} 

def test2(set: Set[Writable], cache: Cache) { 
    set.foreach { write => 
     cache.store(write.value, write.writer) 
    } 
} 

Aquí Scala es capaz de ver que write.value y write.writer tienen el mismo parámetro tipo porque tener el mismo tipo de ruta dependiente.

+0

Gracias por la explicación. Su primera solución no es aplicable, porque cada escritura tendrá un parámetro de tipo diferente. Tu segunda solución es viable. –

+0

Todavía no entiendo realmente lo que está pasando. Ver mi edición a la pregunta. Es por eso que me gusta mantener la pregunta abierta un poco más. –

Cuestiones relacionadas