Was macht ein fauler Val?

247

Mir ist aufgefallen, dass Scala zur Verfügung stellt lazy vals. Aber ich verstehe nicht, was sie tun.

scala> val x = 15
x: Int = 15

scala> lazy val y = 13
y: Int = <lazy>

scala> x
res0: Int = 15

scala> y
res1: Int = 13

Die REPL zeigt, dass dies ein yist lazy val, aber wie unterscheidet es sich von einem normalen val?

kiritsuku
quelle

Antworten:

335

Der Unterschied zwischen ihnen besteht darin, dass a valausgeführt wird, wenn es definiert ist, während a lazy valausgeführt wird, wenn beim ersten Zugriff darauf zugegriffen wird.

scala> val x = { println("x"); 15 }
x
x: Int = 15

scala> lazy val y = { println("y"); 13 }
y: Int = <lazy>

scala> x
res2: Int = 15

scala> y
y
res3: Int = 13

scala> y
res4: Int = 13

Im Gegensatz zu einer Methode (definiert mit def) lazy valwird a einmal und dann nie wieder ausgeführt. Dies kann nützlich sein, wenn ein Vorgang lange dauert und nicht sicher ist, ob er später verwendet wird.

scala> class X { val x = { Thread.sleep(2000); 15 } }
defined class X

scala> class Y { lazy val y = { Thread.sleep(2000); 13 } }
defined class Y

scala> new X
res5: X = X@262505b7 // we have to wait two seconds to the result

scala> new Y
res6: Y = Y@1555bd22 // this appears immediately

Hier werden, wenn die Werte xund ynie verwendet werden, nur xunnötig Ressourcen verschwendet. Wenn wir annehmen, dass dies ykeine Nebenwirkungen hat und wir nicht wissen, wie oft darauf zugegriffen wird (niemals, einmal, tausende Male), ist es sinnlos, dies als zu deklarieren, defda wir es nicht mehrmals ausführen möchten.

Wenn Sie wissen möchten, wie sie lazy valsimplementiert werden, lesen Sie diese Frage .

kiritsuku
quelle
65
Als Ergänzung: @ViktorKlang auf Twitter gepostet: "Wenig bekannte Scala-Tatsache: Wenn die Initialisierung eines faulen Val eine Ausnahme
Peter Schmitz
@ PeterSchmitz Und ich finde das schrecklich. Vergleiche mit Lazy<T>in .NET
Pavel Voronin
61

Diese Funktion verzögert nicht nur teure Berechnungen, sondern ist auch nützlich, um voneinander abhängige oder zyklische Strukturen aufzubauen. Dies führt beispielsweise zu einem Stapelüberlauf:

trait Foo { val foo: Foo }
case class Fee extends Foo { val foo = Faa() }
case class Faa extends Foo { val foo = Fee() }

println(Fee().foo)
//StackOverflowException

Aber mit faulen Vals funktioniert es gut

trait Foo { val foo: Foo }
case class Fee extends Foo { lazy val foo = Faa() }
case class Faa extends Foo { lazy val foo = Fee() }

println(Fee().foo)
//Faa()
Landei
quelle
Es führt jedoch zu derselben StackOverflowException, wenn Ihre toString-Methode das Attribut "foo" ausgibt. Schönes Beispiel für "faul" sowieso !!!
Fuad Efendi
39

Ich verstehe, dass die Antwort gegeben ist, aber ich habe ein einfaches Beispiel geschrieben, um Anfängern wie mir das Verständnis zu erleichtern:

var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)

Die Ausgabe des obigen Codes ist:

x
-----
y
y is: 18

Wie zu sehen ist, wird x gedruckt, wenn es initialisiert wird, aber y wird nicht gedruckt, wenn es auf die gleiche Weise initialisiert wird (ich habe x hier absichtlich als var genommen - um zu erklären, wann y initialisiert wird). Wenn y als nächstes aufgerufen wird, wird es initialisiert und der Wert des letzten 'x' wird berücksichtigt, aber nicht der alte.

Hoffe das hilft.

Mital Pritmani
quelle
35

Ein fauler Wert wird am leichtesten als " auswendig gelernt (no-arg) def" verstanden.

Wie ein Def wird ein Lazy Val erst ausgewertet, wenn er aufgerufen wird. Das Ergebnis wird jedoch gespeichert, sodass nachfolgende Aufrufe den gespeicherten Wert zurückgeben. Das gespeicherte Ergebnis nimmt wie ein Wert Platz in Ihrer Datenstruktur ein.

Wie andere erwähnt haben, bestehen die Anwendungsfälle für einen Lazy Val darin, teure Berechnungen aufzuschieben, bis sie benötigt werden, und ihre Ergebnisse zu speichern und bestimmte zirkuläre Abhängigkeiten zwischen Werten zu lösen.

Lazy Vals werden in der Tat mehr oder weniger als gespeicherte Defs implementiert. Details zur Implementierung finden Sie hier:

http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html

tksfz
quelle
1
vielleicht eher als "memoized def, das 0 Argumente akzeptiert".
Andrey Tyukin
19

Auch lazyist nützlich , ohne zyklische Abhängigkeiten, wie im folgenden Code:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { val x = "Hello" }
Y

Beim Zugriff Ywird jetzt eine Nullzeigerausnahme ausgelöst, da diese xnoch nicht initialisiert ist. Folgendes funktioniert jedoch einwandfrei:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { lazy val x = "Hello" }
Y

BEARBEITEN: Folgendes wird auch funktionieren:

object Y extends { val x = "Hello" } with X 

Dies wird als "früher Initialisierer" bezeichnet. Weitere Informationen finden Sie in dieser SO-Frage .

Jus12
quelle
11
Können Sie klarstellen, warum die Deklaration von Y die Variable "x" im ersten Beispiel nicht sofort initialisiert, bevor Sie den übergeordneten Konstruktor aufrufen?
Ashoat
2
Weil der Superklassenkonstruktor der erste ist, der implizit aufgerufen wird.
Stevo Slavić
@Ashoat Unter diesem Link finden Sie eine Erklärung, warum es nicht initialisiert wird.
Jus12
4

Eine Demonstration der lazy- wie oben definierten - Ausführung bei Definition im Vergleich zur Ausführung beim Zugriff: (unter Verwendung der 2.12.7-Scala-Shell)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t
pjames
quelle
1
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • Alle Werte werden während der Objektkonstruktion initialisiert
  • Verwenden Sie das Schlüsselwort faul, um die Initialisierung bis zur ersten Verwendung zu verschieben
  • Achtung : Lazy Vals sind nicht endgültig und können daher Leistungsnachteile aufweisen

quelle