`lazy val` 在异常情况下表现得像 `def` 是正确的行为吗?

Posted

技术标签:

【中文标题】`lazy val` 在异常情况下表现得像 `def` 是正确的行为吗?【英文标题】:Is it correct behaviour that `lazy val` acts like `def` in case of exception? 【发布时间】:2015-10-22 22:36:37 【问题描述】:

我注意到lazy val 多次重复计算(以防出现异常):

scala> lazy val aaa = println("calc"); sys.error("aaaa")
aaa: Nothing = <lazy>

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at .aaa$lzycompute(<console>:7)
  at .aaa(<console>:7)
  ... 33 elided

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at .aaa$lzycompute(<console>:7)
  at .aaa(<console>:7)
  ... 33 elided

不应该是这样的:

scala> aaa
calc
java.lang.RuntimeException: Not Initialized! 
caused by
java.lang.RuntimeException: aaaa

scala> aaa
java.lang.RuntimeException: Not Initialized! 
caused by
java.lang.RuntimeException: aaaa  

【问题讨论】:

前段时间我是这么读的。 【参考方案1】:

在this 帖子中,他们很好地解释了lazy val 是如何由Scala 编译器编译的。基本上,如果表达式的求值失败,则不会设置 lazy val 包含其数据的指示位。

更新1:

我认为采用第一种方法的一个原因可能是第二种方法可以通过使用两个 lazy vals 来模拟,而无需使用多个 volatile 变量给底层实现带来负担:

scala> lazy val _aaa = Try println("calc"); sys.error("aaaa")
_aaa: scala.util.Try[Nothing] = <lazy>

scala> lazy val aaa = _aaa.get
aaa: Nothing = <lazy>

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at $anonfun$_aaa$1.apply(<console>:10)
  at $anonfun$_aaa$1.apply(<console>:10)
  at scala.util.Try$.apply(Try.scala:191)
  at ._aaa$lzycompute(<console>:10)
  at ._aaa(<console>:10)
  at .aaa$lzycompute(<console>:11)
  at .aaa(<console>:11)
  ... 33 elided

scala> aaa
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at $anonfun$_aaa$1.apply(<console>:10)
  at $anonfun$_aaa$1.apply(<console>:10)
  at scala.util.Try$.apply(Try.scala:191)
  at ._aaa$lzycompute(<console>:10)
  at ._aaa(<console>:10)
  at .aaa$lzycompute(<console>:11)
  at .aaa(<console>:11)
  ... 33 elided

更新2:

正如@Silly Freak 在他的评论中提出的,

scala> lazy val _aaa = Try println("calc"); sys.error("aaaa")
_aaa: scala.util.Try[Nothing] = <lazy>

scala> def aaa = _aaa.get
aaa: Nothing

可能会更好,因为我们可以避免两个lazy vals。

【讨论】:

已经知道它是如何工作的。我的问题是关于方法的正确性还是更好的方法?我相信,没有什么能阻止他们(如我所见)在惰性计算中实现正确的异常处理。 我的意思是,他们可以为 isFailure 提供更多信息(至少)或记住异常(最多,但更昂贵)。目前,0 表示失败/未初始化,因此您无法区分它们。 我不能说清楚,但可能是出于性能原因 (twitter.com/djspiewak/status/302489756552536064)。对我来说,这两种方法似乎都是合理的。虽然不完全相同,但根据您的用例,您可以通过 lazy val aaa = Try println("calc"); sys.error("aaaa") 模拟您的方法 甚至更好:lazy val _aaa = Try println("calc"); sys.error("aaaa") ; lazy val aaa = _aaa.get @kosii _aaaSuccessFailure。如果成功了,那么get没有副作用,可以多次调用。如果失败,则get 抛出异常,因此lazy 无论如何都不会缓存它。如我所见,将aaa 设为lazy val 不会在语义上添加任何内容,不是吗?而且lazy valdef 贵。

以上是关于`lazy val` 在异常情况下表现得像 `def` 是正确的行为吗?的主要内容,如果未能解决你的问题,请参考以下文章

当它应该表现得像 try/catch/finally 时,为啥使用会抛出异常?

img 表现得像背景图片?

如何让数据网格表现得像 ctrl 键处于活动状态?

光滑的滑块箭头在响应模式下表现得像幻灯片

为啥 T-SQL CROSS APPLY 有时表现得像 LEFT JOIN

Python Enum:如何让枚举成员表现得像零一样?