Que fait un val paresseux?

248

J'ai remarqué que Scala fournissait 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 yc'est un lazy val, mais en quoi est-il différent d'un normal val?

kiritsuku
la source

Réponses:

335

La différence entre eux est que a valest exécuté quand il est défini tandis que a lazy valest exécuté quand on y accède la première fois.

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 par def), a lazy valest exécuté une fois, puis plus jamais. Cela peut être utile lorsqu'une opération prend du temps à se terminer et lorsqu'il n'est pas sûr qu'elle soit utilisée ultérieurement.

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 xet yne sont jamais utilisées, ne font que xgaspiller inutilement des ressources. Si nous supposons que cela yn'a pas d'effets secondaires et que nous ne savons pas à quelle fréquence il est consulté (jamais, une fois, des milliers de fois), il est inutile de le déclarer defcar nous ne voulons pas l'exécuter plusieurs fois.

Si vous voulez savoir comment lazy valssont mis en œuvre, consultez cette question .

kiritsuku
la source
@PeterSchmitz Et je trouve ça terrible. Comparer avec Lazy<T>.NET
Pavel Voronin
61

Cette fonctionnalité permet non seulement de retarder des calculs coûteux, mais est également utile pour construire des structures mutuellement dépendantes ou cycliques. Par exemple, cela entraîne 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()
Landei
la source
Mais cela conduira à la même StackOverflowException si votre méthode toString génère l'attribut "foo". Bel exemple de "paresseux" quand même !!!
Fuad Efendi
39

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é ainsi que la valeur du dernier «x» est pris en considération, mais pas l'ancien.

J'espère que cela t'aides.

Mital Pritmani
la source
35

Un val paresseux est plus facilement compris comme un " def mémorisé (sans argument)".

Comme un def, un val paresseux n'est évalué que lorsqu'il est invoqué. Mais le résultat est enregistré de sorte que les invocations suivantes renvoient la valeur enregistrée. Le résultat mémorisé prend de la place dans votre structure de données, comme un val.

Comme d'autres l'ont mentionné, les cas d'utilisation d'un val paresseux sont de différer les calculs coûteux jusqu'à ce qu'ils soient nécessaires et de stocker leurs résultats, et de résoudre certaines dépendances circulaires entre les valeurs.

Les valeurs paresseuses sont en fait implémentées plus ou moins comme des définitions mémorisées. Vous pouvez lire les détails de leur mise en œuvre ici:

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

tksfz
la source
1
peut-être plutôt comme un "def mémorisé qui prend 0 arguments".
Andrey Tyukin
19

Est également lazyutile 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 lèvera Ydésormais une exception de pointeur nul, car il xn'est pas encore initialisé. Cependant, ce qui suit fonctionne bien:

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

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

EDIT: les éléments suivants fonctionneront également:

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

C'est ce qu'on appelle un "initialiseur précoce". Voir cette question SO pour plus de détails.

Jus12
la source
11
Pouvez-vous expliquer pourquoi la déclaration de Y n'initialise pas immédiatement la variable "x" dans le premier exemple avant d'appeler le constructeur parent?
Ashoat
2
Parce que le constructeur de superclasse est le premier à être implicitement appelé.
Stevo Slavić
@Ashoat Veuillez voir ce lien pour une explication de pourquoi il n'est pas initialisé.
Jus12
4

Une démonstration lazy- comme défini ci-dessus - de l'exécution lorsqu'elle est définie par rapport à l'exécution lors de l'accès: (en utilisant le shell scala 2.12.7)

// 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
la source
1
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 le mot-clé paresseux pour différer l'initialisation jusqu'à la première utilisation
  • Attention : les valeurs paresseuses ne sont pas définitives et peuvent donc présenter des inconvénients de performances

la source