web-dev-qa-db-fra.com

Que fait un val paresseux?

J'ai remarqué que Scala fournit lazy vals. Mais je ne comprends pas ce qu'ils font.

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

Le REPL montre que y est un lazy val, mais en quoi est-il différent d'un val normal?

231
kiritsuku

La différence entre eux est qu'une val est exécutée lorsqu'elle est définie, alors qu'un lazy val est exécuté lors de son premier accès.

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

Contrairement à une méthode (définie avec def), un lazy val est exécuté une fois, puis jamais plus. Cela peut être utile lorsqu'une opération prend beaucoup de temps et lorsqu'il n'est pas certain de son utilisation ultérieure.

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

Ici, lorsque les valeurs x et y ne sont jamais utilisées, seul x gaspille inutilement des ressources. Si nous supposons que y n'a pas d'effets secondaires et que nous ne savons pas à quelle fréquence il est utilisé (jamais, des milliers de fois), il est inutile de le déclarer comme def, car nous ne le faisons pas vouloir l'exécuter plusieurs fois.

Si vous voulez savoir comment lazy vals sont implémentés, voyez ceci question .

318
kiritsuku

Cette fonctionnalité permet non seulement de retarder les calculs coûteux, mais est également utile pour construire des structures dépendantes mutuelles ou cycliques. Par exemple. cela conduit à un débordement de pile:

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

Mais avec des vals paresseux cela fonctionne bien

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()
58
Landei

Je comprends que la réponse est donnée mais j’ai écrit un exemple simple pour le rendre facile à comprendre pour les débutants comme moi:

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

La sortie du code ci-dessus est:

x
-----
y
y is: 18

Comme on peut le voir, x est imprimé quand il est initialisé, mais y n'est pas imprimé quand il est initialisé de la même manière (j'ai pris x comme var intentionnellement ici - pour expliquer quand y est initialisé). Ensuite, lorsque y est appelé, il est initialisé et la valeur du dernier "x" est prise en compte, mais pas l’ancien.

J'espère que cela t'aides.

39
Mital Pritmani

Un val paresseux est plus facilement compris comme un " mémoized (no-arg) def".

Comme un def, un val paresseux n'est pas évalué tant qu'il n'est pas appelé. Mais le résultat est enregistré afin que les invocations suivantes renvoient la valeur enregistrée. Le résultat mémo occupe de la place dans votre structure de données, comme un val.

Comme d'autres l'ont mentionné, les cas d'utilisation d'une valeur paresseuse consistent à différer les calculs coûteux jusqu'à leur utilisation, à stocker leurs résultats et à résoudre certaines dépendances circulaires entre les valeurs.

Les valeurs paresseuses sont en fait plus ou moins mises en œuvre comme des mémos. Vous pouvez lire sur les détails de leur mise en œuvre ici:

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

32
tksfz

De plus, lazy est utile sans dépendances cycliques, comme dans le code suivant:

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

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

L'accès à Y lève maintenant une exception de pointeur null, car x n'est pas encore initialisé. Ce qui suit fonctionne cependant bien:

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

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

EDIT: ce qui suit fonctionnera également:

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

Ceci est appelé un "initialisateur précoce". Voir this SO question pour plus de détails.

19
Jus12

Une démonstration de lazy - telle que définie ci-dessus - exécution lorsque défini vs exécution lors de l'accès: (à l'aide de 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
3
pjames
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • Tous les vals sont initialisés lors de la construction de l'objet
  • Utilisez un mot clé lazy pour différer l'initialisation jusqu'à la première utilisation
  • Attention: Les valeurs paresseuses ne sont pas finales et peuvent donc présenter des inconvénients en termes de performances
1
user2989087