Apa yang dilakukan val malas?

248

Saya perhatikan bahwa Scala menyediakan lazy vals. Tapi saya tidak mengerti apa yang mereka lakukan.

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

The REPL menunjukkan bahwa yadalah lazy val, tapi bagaimana itu berbeda dari yang normal val?

kiritsuku
sumber

Jawaban:

335

Perbedaan di antara mereka adalah, bahwa a valdieksekusi ketika didefinisikan sedangkan a lazy valdieksekusi ketika diakses pertama kali.

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

Berbeda dengan metode (didefinisikan dengan def) a lazy valdieksekusi sekali dan kemudian tidak pernah lagi. Ini bisa bermanfaat ketika operasi membutuhkan waktu lama untuk diselesaikan dan ketika tidak yakin apakah nanti digunakan.

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

Di sini, ketika nilai-nilai xdan ytidak pernah digunakan, hanya xmembuang-buang sumber daya yang tidak perlu. Jika kita mengira itu ytidak memiliki efek samping dan bahwa kita tidak tahu seberapa sering itu diakses (tidak pernah, sekali, ribuan kali) tidak ada gunanya untuk menyatakannya defkarena kita tidak ingin menjalankannya beberapa kali.

Jika Anda ingin tahu bagaimana lazy valspenerapannya, lihat pertanyaan ini .

kiritsuku
sumber
@ PeterSchmitz Dan saya menemukan ini mengerikan. Bandingkan dengan Lazy<T>di .NET
Pavel Voronin
61

Fitur ini membantu tidak hanya menunda perhitungan mahal, tetapi juga berguna untuk membangun struktur yang saling bergantung atau siklik. Misalnya ini mengarah ke stack overflow:

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

Tetapi dengan malas vals berfungsi dengan baik

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
sumber
Tapi itu akan mengarah ke StackOverflowException yang sama jika metode toString Anda menghasilkan atribut "foo". Contoh bagus dari "malas" pula !!!
Fuad Efendi
39

Saya mengerti bahwa jawabannya diberikan tetapi saya menulis contoh sederhana untuk membuatnya mudah dipahami untuk pemula seperti saya:

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

Output dari kode di atas adalah:

x
-----
y
y is: 18

Seperti dapat dilihat, x dicetak ketika diinisialisasi, tetapi y tidak dicetak ketika diinisialisasi dengan cara yang sama (saya sengaja mengambil x sebagai var di sini - untuk menjelaskan kapan Anda diinisialisasi). Selanjutnya ketika y dipanggil, itu diinisialisasi serta nilai 'x' terakhir dipertimbangkan tetapi bukan yang lama.

Semoga ini membantu.

Mital Pritmani
sumber
35

Lazy val paling mudah dipahami sebagai " memo memoized (no-arg)".

Seperti def, val malas tidak dievaluasi sampai dipanggil. Tetapi hasilnya disimpan sehingga doa berikutnya mengembalikan nilai yang disimpan. Hasil memoized mengambil ruang dalam struktur data Anda, seperti val.

Seperti yang telah disebutkan orang lain, kasus penggunaan untuk lazy val adalah menunda perhitungan yang mahal sampai mereka diperlukan dan menyimpan hasilnya, dan untuk menyelesaikan dependensi melingkar tertentu antara nilai.

Valk Malas sebenarnya diimplementasikan lebih atau kurang sebagai def memoized. Anda dapat membaca tentang detail penerapannya di sini:

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

tksfz
sumber
1
mungkin lebih sebagai "memo memoized yang mengambil 0 argumen".
Andrey Tyukin
19

Juga lazyberguna tanpa ketergantungan siklik, seperti pada kode berikut:

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

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

Mengakses Ysekarang akan melempar pengecualian pointer nol, karena xbelum diinisialisasi. Namun, berikut ini berfungsi dengan baik:

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

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

Sunting: yang berikut ini juga akan berfungsi:

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

Ini disebut "penginisialisasi awal". Lihat pertanyaan SO ini untuk lebih jelasnya.

Jus12
sumber
11
Bisakah Anda menjelaskan mengapa deklarasi Y tidak langsung menginisialisasi variabel "x" pada contoh pertama sebelum memanggil konstruktor induk?
Ashoat
2
Karena superclass constructor adalah yang pertama yang dipanggil secara implisit.
Stevo Slavia
@Ashoat Silakan lihat tautan ini untuk penjelasan mengapa itu tidak diinisialisasi.
Jus12
4

Demonstrasi dari lazy- sebagaimana didefinisikan di atas - eksekusi ketika didefinisikan vs eksekusi ketika diakses: (menggunakan 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
sumber
1
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • Semua val diinisialisasi selama konstruksi objek
  • Gunakan kata kunci malas untuk menunda inisialisasi hingga penggunaan pertama
  • Perhatian : Mal malas tidak final dan karena itu mungkin menunjukkan kelemahan kinerja

sumber