[scala] 게으른 발은 무엇을합니까?

나는 Scala가 제공하는 것을 알아 차렸다 lazy vals. 그러나 나는 그들이 무엇을 얻지 못합니다.

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의 쇼가 yA는 lazy val하지만, 어떻게 정상적인 다르다 val?



답변

이들의 차이점은 a val는 정의 lazy val될 때 실행되는 반면 a 는 처음 액세스 할 때 실행된다는 것입니다.

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

(으로 정의 된 def) 메소드와 달리 a lazy val는 한 번 실행 된 후 다시는 실행되지 않습니다. 작업을 완료하는 데 시간이 오래 걸리고 나중에 사용되는지 확실하지 않은 경우에 유용합니다.

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

여기서 값 xy사용하지 않으면 x자원을 불필요하게 낭비합니다. 우리가 가정하는 경우 y부작용이없고, 우리가 액세스 빈도를 모르는 (결코 한 번, 수천 번)는로 선언하는 쓸모없는 def우리가 여러 번 실행하지 않으입니다.

lazy vals구현 방법을 알고 싶다면 이 질문을 참조하십시오 .


답변

이 기능은 값 비싼 계산을 지연시킬뿐만 아니라 상호 의존적이거나 주기적 구조를 구성하는 데 유용합니다. 예를 들어 스택 오버플로가 발생합니다.

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

그러나 게으른 발에는 잘 작동합니다.

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


답변

나는 대답이 주어진다는 것을 이해하지만 나 같은 초보자도 쉽게 이해할 수 있도록 간단한 예를 썼습니다.

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

위 코드의 출력은 다음과 같습니다.

x
-----
y
y is: 18

보시다시피, x는 초기화 될 때 인쇄되지만 동일한 방식으로 초기화 될 때 y는 인쇄되지 않습니다 (y를 초기화 할 때 설명하기 위해 의도적으로 x를 var로 사용했습니다). 다음으로 y가 호출되면 초기화되고 마지막 ‘x’의 값이 고려되지만 이전 값은 고려되지 않습니다.

도움이 되었기를 바랍니다.


답변

게으른 val은 ” memoized (no-arg) def” 로 가장 쉽게 이해됩니다 .

데프처럼 게으른 val은 호출 될 때까지 평가되지 않습니다. 그러나 후속 호출이 저장된 값을 리턴하도록 결과가 저장됩니다. 메모 된 결과는 데이터 구조에서 val과 같은 공간을 차지합니다.

다른 사람들이 언급했듯이, 게으른 값의 유스 케이스는 필요할 때까지 값 비싼 계산을 연기하고 결과를 저장하고 값 사이의 특정 순환 종속성을 해결하는 것입니다.

게으른 val은 실제로 메모로 정의 된 defs로 구현됩니다. 구현에 대한 자세한 내용은 여기를 참조하십시오.

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


답변

lazy다음 코드와 같이 순환 종속성이없는 경우 에도 유용합니다.

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

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

아직 초기화되지 않았 Y으므로 액세스 하면 null 포인터 예외가 발생 x합니다. 그러나 다음은 정상적으로 작동합니다.

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

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

편집 : 다음도 작동합니다.

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

이것을 “초기 이니셜 라이저”라고합니다. 자세한 내용은 이 SO 질문 을 참조하십시오.


답변

lazy위에서 정의한대로 정의시 실행과 액세스시 실행 시연 : (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


답변

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

scala> lazyEight
I am lazy !
res1: Int = 8
  • 모든 val은 객체 생성 중에 초기화됩니다
  • lazy 키워드를 사용하여 처음 사용할 때까지 초기화 연기
  • 주의 : 게으른 값은 최종적이지 않으므로 성능 저하가 나타날 수 있습니다.