2012-04-15 5 views
5

Ich habe versucht zu verstehen, wie implizite Parameter in Scala funktionieren. So weit ich weiß, geht die implizite Parameterauflösung ungefähr so:Lazy Vals und implizite Parameter in Scala

  1. Explizites Übergeben eines Objekts an die Methode.
  2. implizite Definitionen im Geltungsbereich definiert.
  3. Companion Objekt der Klasse als impliziten Parameter

jedoch verwendet, wenn ich mit diesem in Verbindung faul vals Herumspielen begonnen habe ich ein bisschen ein supprise. Es scheint, dass Lazy Vals nur die letzten Auflösungsregeln verwenden. Hier finden Sie einige Codebeispiele veranschaulichen:

class Bar(val name:String) 
object Bar { implicit def bar = new Bar("some default bar") } 

class Foo { 
    lazy val list = initialize 
    def initialize(implicit f:Bar) = { 
    println("initialize called with Bar: '" + f.name + "' ...") 
    List[Int]() 
    } 
} 

trait NonDefaultBar extends Foo { 
    implicit def f = new Bar("mixed in implicit bar") 
    def mixedInInit = initialize 
    lazy val mixedInList = list 
} 

object Test { 
    def test = { 
     println("Case 1: with implicitp parameter from companion object") 
     val foo1 = new Foo 
     foo1.list 
     foo1.initialize 

     println("Case 2: with mixedin implicit parameter overriding the default one...") 
     val foo2 = new Foo with NonDefaultBar 
     foo2.mixedInList 

     val foo3 = new Foo with NonDefaultBar 
     foo3.mixedInInit 

     println("Case 3: with local implicit parameter overriding the default one...") 
     implicit def nonDefaultBar = new Bar("locally scoped implicit bar") 
     val foo4 = new Foo 
     foo4.list 
     foo4.initialize 
    } 
} 

Aufruf Test.test gibt die folgende Ausgabe:

Case 1: with implicitp parameter from companion object 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'some default bar' ... 
Case 2: with mixedin implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'mixed in implicit bar'... 
Case 3: with local implicit parameter overriding the default one... 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'locally scoped implicit bar' ... 

Warum der Compiler nicht fängt, dass es eine implict Bar ist gemischt beim Aufruf mixedInList in Fall 2. In Fall 3 fehlt auch der lokal definierte implizite Balken beim Zugriff auf die Liste.

Gibt es Möglichkeiten, implizite Parameter mit Lazy Vals zu verwenden, die das im Begleitobjekt definierte implizite Parameter nicht verwenden?

Antwort

4

Das ist, weil es keine andere gibt, wenn der Compiler die Foo-Klasse kompiliert. Der dekompilierten Code in Java wie folgt aussehen:

public class Foo 
    implements ScalaObject 
{ 
    private List<Object> list; 
    public volatile int bitmap$0; 

    public List<Object> list() 
    { 
    if (
     (this.bitmap$0 & 0x1) == 0); 
    synchronized (this) 
    { 
     if (
     (this.bitmap$0 & 0x1) == 0) { 
     this.list = initialize(Bar..MODULE$.bar()); this.bitmap$0 |= 1; } return this.list; 
    } 
    } 
    public List<Object> initialize(Bar f) { Predef..MODULE$.println(new StringBuilder().append("initialize called with Bar: '").append(f.name()).append("' ...").toString()); 
    return Nil..MODULE$; 
    } 
} 

Die lazy val ist nur eine Methode, die, wenn die Variable prüft bereits gesetzt ist und entweder gibt es, oder es setzt und dann gibt es zurück. Dein Mixin wird also überhaupt nicht berücksichtigt. Wenn Sie das wollen, müssen Sie sich selbst um die Initialisierung kümmern.

+0

Ok, aber * sollte nicht * es die implicits erkennen? Sie sind bei Compiletime bekannt, richtig? Vielleicht würde es funktionieren, wenn ich den impliziten Parameter zum Konstruktor verschiebe? –

+0

Es erkennt den einzigen impliziten Balken, der zum Zeitpunkt der Kompilierung im Gültigkeitsbereich ist, und das ist der im Bar-Begleiter. Da 'list' nur auf' Foo' definiert ist, wird immer dieses verwendet. Sie könnten das implizite in Ihrem Konstruktor definieren, aber das implizite in dem Merkmal wäre immer noch nicht im Gültigkeitsbereich, da es nur für die Instanz existiert. – drexin

+0

Also, wenn ich die Methode lazy val und initialize auf das 'NonDefaultBar'-Merkmal verschiebe und dieses Merkmal' Foo' erweitern lasse und auch das implizite def nach 'Foo' verschiebe, dann würde ich den Compiler das implizite def in' Foo greifen 'bei der Auswertung der' Lazy Val List'? –

0

Obwohl scala keine Lazy Vals mit impliziten Parametern unterstützt, können Sie dies selbst mithilfe von Optionen definieren. Daher ist eine Lösung zu ersetzen:

lazy val list = initialize 

von

private var _list: Option[List[Int]] = None 
def list(implicit f: Bar) = 
    _list.getOrElse{ 
    _list = Some(initialize) 
    _list.get 
    } 

Dann Test.test zeigt das erwartete Ergebnis läuft:

Case 1: with implicitp parameter from companion object 
initialize called with Bar: 'some default bar' ... 
initialize called with Bar: 'some default bar' ... 
Case 2: with mixedin implicit parameter overriding the default one... 
initialize called with Bar: 'mixed in implicit bar' ... 
initialize called with Bar: 'mixed in implicit bar' ... 
Case 3: with local implicit parameter overriding the default one... 
initialize called with Bar: 'locally scoped implicit bar' ... 
initialize called with Bar: 'locally scoped implicit bar' ... 

Beachten Sie, dass, wenn Sie mutable options hatte, können Sie Ihre faul ersetzen könnte val mit nur zwei Zeilen, um das gleiche Ergebnis zu erhalten.

private val _list: MutableOpt[List[Int]] = MutableOpt.from(None) 
def list(implicit f: Bar) = _list.getOrSet(initialize) 

Persönlich hoffe ich eines Tages Scala uns dies mit einer Zeile lassen schreiben:

lazy val list(implicit f: Bar) = initialize 

die perfekt sinnvoll wäre: Sie können jederzeit den Wert der Variablenliste zugreifen möchten, Sie benötigen einen Balken im Bereich, obwohl nur die erste Berechnung von Bedeutung ist.