2012-07-01 9 views
9

Hier ist ein einfacher Aufbau mit zwei Merkmalen, einer Klasse mit einem kovarianten Typparameter, der durch die vorherigen Eigenschaften begrenzt ist, und einer zweiten Klasse mit einem Typparameter, der von der anderen Klasse begrenzt wird. Für beide Klassen ist eine bestimmte Methode nur dann verfügbar (über implizite Beweise), wenn einer der beiden Merkmale dem Typparameter zugrunde liegt. Dies kompiliert fein:Scala: Impliziter Beweis für die Klasse mit dem Typparameter

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 
} 

Da jedoch Bar covariant ist in F, ich sollte die F Parameter in Grill nicht benötigen. Ich sollte nur verlangen, dass B ist ein Untertyp von Bar[ReadableFoo]. Dies ist jedoch nicht:

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

mit dem Fehler:

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

Warum sind die impliziten Beweise nicht berücksichtigt werden?

Antwort

6

Der Aufruf bar.readField ist möglich, weil die Beweise Instanz <:< eine implizite Konvertierung B-Bar[ReadableFoo] ermöglicht.

Das Problem, das ich denke, dass readField Sie benötigen einen sukzessiven Beweisparameter F <:< ReadableFoo. Also meine Vermutung ist, dass der Compiler den Typ-Parameter Bar in der ersten Suchstufe der impliziten Auflösung nicht vollständig ersetzt (denn um readField zu finden, benötigt es nur irgendeinen Bar an erster Stelle). Und dann erstickt es an der zweiten impliziten Resolution, denn soweit ich weiß, gibt es keine Form von "Backtracking".

Wie auch immer. Die gute Sache ist, Sie mehr als die Compiler wissen, und Sie können die Konvertierung explizit engagieren, entweder durch die apply Methode von <:<, oder durch die Hilfsmethode unter Verwendung von 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 
} 

Es gibt eine andere Möglichkeit ist, die sein könnte die sauberste, da sie nicht über die Umsetzung der <:< angewiesen, was ein Problem sein könnte, wie @Kaito schlägt vor:

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

Ich bin nicht sicher, ob <: Kaito

+0

@Kaito: Haben Sie irgendwelche Beweise, dass '<: <' '' apply'' nicht gemeint ist? Es ist [dokumentiertes Verhalten] (http://www.scala-lang.org/api/rc/scala/Predef$$$less$colon$less.html). Sie könnten '(bar: Bar [ReadableFoo]) .readField' schreiben und die implizite Konvertierung automatisch einleiten, aber Sciss 'Version fühlt sich für mich sauberer an. –

+0

@TravisBrown: Keine. Aber ich kann nicht die Linie finden, die das Verhalten der Funktion verbal dokumentiert, sondern nur die vererbte Erklärung der abstrakten Eigenschaft Function1. Ich stimme zu, dass es nett ist, aber ich bin mir nicht sicher, ob dieses Verhalten zuverlässig ist, es könnte auch eine Ausnahme in der nächsten Version werfen, denke ich. – Kaito

5

0 __ 's Antwort (verwenden sie die implizite Beweise Argument bar in die richtige Art zu drehen) ist die Antwort Ich würde die spezifische Aufgabe geben Ich fragte Sie (obwohl ich vorschlagen würde, nicht implicitly zu verwenden, wenn Sie das implizite Argument genau dort sitzen haben).

Es ist erwähnenswert, dass die Situation, die Sie beschreiben, klingt, als wäre es ein nützlicher Anwendungsfall für Ad-hoc-Polymorphismus über Typklassen.Sagen Sie zum Beispiel, dass wir die folgenden Setup haben:

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

Und eine Typklasse, zusammen mit einigen Komfortmethoden für neue Instanzen zu schaffen und für ein readField Verfahren auf jede Art Zuhälterei, die eine Instanz in ihrem Umfang hat:

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 

Und ein paar Beispiele:

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

Und schließlich ein lesbares Foo:

case class MyFoo(x: Int) extends Foo 

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

Dies erlaubt uns folgendes zu tun, zum Beispiel:

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 
      ^

Was ist das, was wir wollen.

1

Das ist keine Antwort auf die Frage, sondern um zu zeigen, dass die ‚Art-Einschränkung‘ wirklich nur eine implizite Konvertierung ist:

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

Ich bin mir bewusst, wie es funktioniert. Die Frage, die ich stellte, ist einfach, ob das beabsichtigte Verhalten ist und in zukünftigen Updates zuverlässig sein kann. In der Dokumentation wird lediglich erwähnt, dass es sich um Typbeschränkungen handelt, nicht um die Verwendung als implizite Konvertierungen. – Kaito

Verwandte Themen