Tembel bir val ne yapar?


248

Scala'nın bunu sağladığını fark ettim lazy vals. Ama ne yaptıklarını anlamıyorum.

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

REPL gösterileri o ybir olduğunu lazy val, ama nasıl normal farklıdır val?

Yanıtlar:


335

Aralarındaki fark, a valtanımlandığında lazy valyürütülürken, a ilk kez erişildiğinde yürütülür.

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

Bir yöntemin aksine (ile tanımlanır def) a bir lazy valkez ve daha sonra bir daha yürütülür. Bu, bir işlemin tamamlanması uzun zaman aldığında ve daha sonra kullanılıp kullanılmadığından emin olmadığında yararlı olabilir.

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

Burada, değerler xve yasla kullanılmadığında, sadece xgereksiz yere kaynakları boşa harcar. Bunun yherhangi bir yan etkisi olmadığını ve ne sıklıkta erişildiğini bilmediğimizi varsayarsak (asla, bir kez, binlerce kez), defbunu birkaç kez yürütmek istemediğimiz için ilan etmek işe yaramaz .

Nasıl lazy valsuygulandığını bilmek istiyorsanız , bu soruya bakın .


65
Ek olarak: @ViktorKlang Twitter'da yayınladı: "Az bilinen Scala gerçeği: tembel bir
Peter Schmitz

@PeterSchmitz Ve bunu korkunç buluyorum. Lazy<T>.NET ile karşılaştır
Pavel Voronin

61

Bu özellik sadece pahalı hesaplamaları geciktirmekle kalmaz, aynı zamanda karşılıklı bağımlı veya döngüsel yapılar oluşturmak için de yararlıdır. Örneğin, bu bir yığın taşmasına yol açar:

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

Ancak tembel vals ile iyi çalışıyor

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

Ancak, toString yönteminiz "foo" özniteliği çıkarırsa aynı StackOverflowException özelliğine neden olur. Yine de "tembel" güzel örnek !!!
Fuad Efendi

39

Cevabın verildiğini anlıyorum ama benim gibi yeni başlayanlar için anlaşılmasını kolaylaştırmak için basit bir örnek yazdım:

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

Yukarıdaki kodun çıktısı:

x
-----
y
y is: 18

Görülebileceği gibi, x başlatıldığında yazdırılır, ancak y aynı şekilde başlatıldığında yazdırılmaz (x'i burada kasten değişken olarak aldım - y başlatıldığında açıklamak için). Daha sonra y çağrıldığında, başlatılır ve son 'x' değeri dikkate alınır, ancak eskisi değil.

Bu yardımcı olur umarım.


35

Tembel bir val en kolay şekilde " memoized (no-arg) def" olarak anlaşılır .

Bir def gibi, tembel bir val çağrılıncaya kadar değerlendirilmez. Ancak sonuç kaydedilir, sonraki çağrılar kaydedilen değeri döndürür. Notlanan sonuç, veri yapınızda bir val gibi yer kaplar.

Diğerlerinin de belirttiği gibi, tembel bir val için kullanım durumları, pahalı hesaplamaları gerekene kadar ertelemek ve sonuçlarını saklamak ve değerler arasındaki belirli dairesel bağımlılıkları çözmek içindir.

Tembel vallar aslında az ya da çok kaydedilmiş defs olarak uygulanır. Uygulamalarının ayrıntılarını buradan okuyabilirsiniz:

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


1
belki de "0 argüman alan notlu bir def".
Andrey Tyukin

19

Ayrıca lazyşu kodda olduğu gibi, siklik bağımlılıklar olmadan yararlıdır:

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

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

Henüz başlatılmadığından erişim Yartık boş gösterici istisnası atacak x. Bununla birlikte, aşağıdakiler iyi çalışır:

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

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

EDIT: aşağıdakiler de çalışır:

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

Buna "erken başlatıcı" denir. Daha fazla ayrıntı için bu SO sorusuna bakın.


11
Üst yapıcı çağrılmadan önce, Y bildiriminin neden ilk örnekte "x" değişkenini hemen başlatmadığını açıklayabilir misiniz?
Ashoat

2
Çünkü üst sınıf yapıcı örtülü olarak adlandırılan ilk yapıcıdır.
Stevo Slavić

@ Ashoat Neden başlatılmadığını açıklamak için lütfen bu bağlantıya bakın .
Jus12

4

Bir gösteri lazy- yukarıda tanımlandığı gibi - çalıştırma vs tanımlanan zaman erişildiğinde yürütülmesi: (2.12.7 Scala kabuk kullanılarak)

// 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

1
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • Tüm valler nesne yapımı sırasında başlatılır
  • İlk kullanıma kadar başlatmayı ertelemek için tembel anahtar kelime kullanın
  • Dikkat : Tembel valler kesin değildir ve bu nedenle performans dezavantajları gösterebilir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.