2012-07-01 17 views
9

Ecco una semplice impostazione con due tratti, una classe con un parametro di tipo covariante limitato dai tratti precedenti e una seconda classe con un parametro di tipo limitato dall'altra classe. Per entrambe le classi, è disponibile un metodo particolare (tramite prove implicite) solo se uno dei due tratti è alla base del parametro type. Questo compila bene:Scala: Evidenza implicita per classe con parametro 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 
} 

Tuttavia, dal momento che Bar è covariante nel F, non avrei bisogno di parametro F in Grill. Dovrei semplicemente richiedere che B sia un sottotipo di Bar[ReadableFoo]. Questo, tuttavia, fallisce:

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

con l'errore:

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

Perché non è la prova implicita di essere presi in considerazione?

risposta

6

La chiamata bar.readField è possibile perché l'istanza prove <:< permette una conversione implicita da B a Bar[ReadableFoo].

Il problema Penso che per chiamare readField sia necessario un parametro di prova successivo F <:< ReadableFoo. Quindi, suppongo che il compilatore non sostituisca completamente il parametro di tipo Bar nella prima fase di ricerca della risoluzione implicita (poiché per trovare readField, è necessario solo uno qualsiasi Bar in primo luogo). E poi soffoca sulla seconda risoluzione implicita, perché non c'è alcuna forma di 'backtracking' per quanto ne so.

In ogni caso. La cosa buona è, si sa più che il compilatore e si può coinvolgere la conversione in modo esplicito, sia utilizzando il metodo di <:<apply, oppure utilizzando il metodo di supporto 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 
} 

C'è un'altra possibilità che potrebbe essere il più pulito, in quanto non si basa sulla realizzazione di <:< che potrebbe essere un problema in quanto suggerisce @Kaito:

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

io non sono sicuro se <: <è destinato a essere chiamato, è semplicemente un valore che esiste se il primo parametro è un sottotipo del secondo. L'implementazione è fondamentalmente la funzione di identità. Questi esempi funzionano perché fondamentalmente fanno un typecast (sicuro) che aiuta il compilatore a trovare il parametro implicito per il metodo readField. – Kaito

+0

@Kaito: hai qualche prova che '<: <' 's apply non è stato chiamato? È [comportamento documentato] (http://www.scala-lang.org/api/rc/scala/Predef$$$less$colon$less.html). Potresti scrivere '(bar: Bar [ReadableFoo]). ReadField' e lasciare che la conversione implicita si avvii automaticamente, ma la versione di Sciss mi sembra più pulita. –

+0

@TravisBrown: Nessuno. Ma non riesco a trovare la linea che in realtà documenta verbalmente il comportamento della funzione, solo la spiegazione ereditata del tratto astratto Function1. Sono d'accordo che è bello, ma non sono sicuro che si possa fare affidamento su questo comportamento, ma potrebbe anche essere un'eccezione nella prossima versione che penso. – Kaito

5

0 __ (utilizzare l'argomento prove implicita di trasformare bar nel tipo a destra) è la risposta Darei alla ricerca specifica ti ho chiesto (anche se suggerirei di non usare implicitly se hai la discussione implicita seduta proprio lì).

Vale la pena notare che la situazione che stai descrivendo suona come potrebbe essere un buon caso per il polimorfismo ad-hoc tramite classi di tipi, tuttavia.Diciamo per esempio, che abbiamo la seguente configurazione:

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

E una classe tipo, insieme ad alcuni metodi di convenienza per la creazione di nuove istanze e per sfruttamento della prostituzione un metodo readField su qualsiasi tipo che ha un esempio nel campo di applicazione:

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 

E un paio di casi:

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

E infine una leggibile Foo:

case class MyFoo(x: Int) extends Foo 

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

Questo ci permette di effettuare le seguenti operazioni, ad esempio:

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 
      ^

che è quello che vogliamo.

1

Questa non è una risposta alla domanda, ma per mostrare che il 'tipo di vincolo' è in realtà solo una conversione implicita:

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

Sono consapevole di come funziona. La domanda che stavo ponendo è semplicemente se si tratta di un comportamento previsto e può essere fatto valere in futuri aggiornamenti. La documentazione menziona solo che sono vincoli di tipo, non il loro utilizzo come conversioni implicite. – Kaito

Problemi correlati