2012-07-01 4 views
9

Aquí hay una configuración simple con dos rasgos, una clase con un parámetro de tipo covariante delimitado por los rasgos anteriores y una segunda clase con un parámetro de tipo delimitado por la otra clase. Para ambas clases, un método particular está disponible (a través de evidencia implícita) solo si uno de los dos rasgos subyace en el parámetro de tipo. Este compila bien:Scala: evidencia implícita para la clase con el parámetro de tipo

trait Foo 
trait ReadableFoo extends Foo {def field: Int} 

case class Bar[+F <: Foo](foo: F) { 
    def readField(implicit evidence: F <:< ReadableFoo) = foo.field 
} 

case class Grill[+F <: Foo, +B <: Bar[F]](bar: B) { 
    def readField(implicit evidence: F <:< ReadableFoo) = bar.readField 
} 

Sin embargo, desde Bar es covariante en F, no debería necesitar el parámetro F en Grill. Solo debo exigir que B sea un subtipo de Bar[ReadableFoo]. Esto, sin embargo, falla:

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField 
} 

con el error:

error: Cannot prove that Any <:< this.ReadableFoo. 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField 

¿Por qué no se están tomando las pruebas implícita en cuenta?

Respuesta

6

La llamada bar.readField es posible porque la instancia evidencia <:< permite una conversión implícita B-Bar[ReadableFoo].

El problema creo que para llamar readField necesita un parámetro de pruebas sucesivas F <:< ReadableFoo. Así que mi conjetura es que el compilador no sustituye completamente el parámetro tipo de Bar en la primera etapa de búsqueda de la resolución implícita (porque para encontrar readField, solo requiere Bar en primer lugar). Y luego se ahoga en la segunda resolución implícita, porque no hay forma de 'retroceder' hasta donde yo sé.

De todos modos. Lo bueno es que, usted sabe más que el compilador y puede involucrar la conversión explícita, ya sea utilizando el método de <:<apply, o utilizando el método de ayuda implicitly:

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = evidence(bar).readField 
} 

case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = 
    implicitly[Bar[ReadableFoo]](bar).readField 
} 

Hay otra posibilidad que podría ser el más limpio, ya que no se basa en la implementación de <:< que podría ser un problema, ya que @Kaito sugiere:

respuesta
case class Grill[+B <: Bar[_]](bar: B) { 
    def readField(implicit evidence: B <:< Bar[ReadableFoo]) = 
    (bar: Bar[ReadableFoo]).readField 
} 
+0

No estoy seguro de si <: Kaito

+0

@Kaito: ¿Tiene alguna evidencia de que '<: <' s 'apply' no se llame? Es [comportamiento documentado] (http://www.scala-lang.org/api/rc/scala/Predef$$$less$colon$less.html). Podrías escribir '(bar: Bar [ReadableFoo])., ReadField' y dejar que la conversión implícita se active automáticamente, pero la versión de Sciss me parece más limpia. –

+0

@TravisBrown: ninguna. Pero no puedo encontrar la línea que en realidad documenta verbalmente el comportamiento de la función, solo la explicación heredada del rasgo abstracto de Function1. Estoy de acuerdo en que es bueno, pero no estoy seguro si se puede confiar en ese comportamiento, también podría arrojar una excepción en la próxima versión, creo. – Kaito

5

0 __ 's (utilizar el argumento de la evidencia implícita para convertir bar en el tipo correcto) es la respuesta Daría a la búsqueda específica Si usted ha preguntado (aunque yo sugeriría no usar implicitly si tiene el argumento implícito sentado allí).

Vale la pena señalar que la situación que está describiendo parece que podría ser un buen caso de uso para el polimorfismo ad-hoc mediante clases de tipo, sin embargo.Digamos por ejemplo que tenemos la siguiente configuración:

trait Foo 
case class Bar[F <: Foo](foo: F) 
case class Grill[B <: Bar[_]](bar: B) 

Y un tipo de clase, junto con algunos métodos de conveniencia para la creación de nuevas instancias y por proxenetismo un método readField en cualquier tipo que tiene una instancia en su alcance:

trait Readable[A] { def field(a: A): Int } 

object Readable { 
    def apply[A, B: Readable](f: A => B) = new Readable[A] { 
    def field(a: A) = implicitly[Readable[B]].field(f(a)) 
    } 

    implicit def enrich[A: Readable](a: A) = new { 
    def readField = implicitly[Readable[A]].field(a) 
    } 
} 

import Readable.enrich 

Y un par de ejemplos:

implicit def barInstance[F <: Foo: Readable] = Readable((_: Bar[F]).foo) 
implicit def grillInstance[B <: Bar[_]: Readable] = Readable((_: Grill[B]).bar) 

Y por último, una lectura Foo:

case class MyFoo(x: Int) extends Foo 

implicit object MyFooInstance extends Readable[MyFoo] { 
    def field(foo: MyFoo) = foo.x 
} 

Esto nos permite hacer lo siguiente, por ejemplo:

scala> val readableGrill = Grill(Bar(MyFoo(11))) 
readableGrill: Grill[Bar[MyFoo]] = Grill(Bar(MyFoo(11))) 

scala> val anyOldGrill = Grill(Bar(new Foo {})) 
anyOldGrill: Grill[Bar[java.lang.Object with Foo]] = Grill(Bar([email protected])) 

scala> readableGrill.readField 
res0: Int = 11 

scala> anyOldGrill.readField 
<console>:22: error: could not find implicit value for evidence parameter of 
type Readable[Grill[Bar[java.lang.Object with Foo]]] 
       anyOldGrill.readField 
      ^

que es lo que queremos.

1

Ésta no es una respuesta a la pregunta, sino para mostrar que el 'tipo de restricción' es en realidad una conversión implícita:

Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_33). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> trait A { def test() {} } 
defined trait A 

scala> class WhatHappens[T] { def test(t: T)(implicit ev: T <:< A) = t.test() } 
defined class WhatHappens 

scala> :javap -v WhatHappens 
... 
public void test(java.lang.Object, scala.Predef$$less$colon$less); 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_2 
    1: aload_1 
    2: invokeinterface #12, 2; //InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object; 
    7: checkcast #14; //class A 
    10: invokeinterface #17, 1; //InterfaceMethod A.test:()V 
    15: return 
... 
    LocalVariableTable: 
    Start Length Slot Name Signature 
    0  16  0 this  LWhatHappens; 
    0  16  1 t  Ljava/lang/Object; 
    0  16  2 ev  Lscala/Predef$$less$colon$less; 
... 
+0

Soy consciente de cómo funciona. La pregunta que estaba haciendo es simplemente si ese es el comportamiento previsto y se puede confiar en futuras actualizaciones. La documentación menciona solo que son restricciones de tipo, no su uso como conversiones implícitas. – Kaito

Cuestiones relacionadas