为什么 Scala 的 LazyList 的元素在计算后显示为未评估?

Why are Scala's LazyList's elements displayed as unevaluated after being computed?

我对 Scala 完全陌生。我一直在玩 LazyLists。考虑以下因素:

val fun: Int => Int = (x: Int) => {
    println("PROCESSING...")
    x + 1
}

val lazyList = LazyList(fun(1), fun(2), fun(3))

上面的代码片段打印了三次 "PROCESSING...",这表明计算了 LazyList 的所有三个元素。我发现这种行为对于惰性集合来说是相当出乎意料的。所以,我决定打印它:

println(lazyList) // which prints "LazyList(<not computed>)".

我以为它会打印出来LazytList(2, 3, 4)。 (我不完全确定,但在我看来,Scala 的 println 可以 用于惰性集合 类似 的 [= GHCi 中的 25=] 命令,将集合分为两部分:评估的和未评估的。)

所以,这是我关于这段代码的问题:

  1. 为什么没有显示已评估的元素?如果它们确实未被评估,那么这个三元组 "PROCESSING..." 是关于什么的?如果不是,为什么 println 这么说?
  2. 为什么我们要立即计算 LazyList 的参数,例如 fun(1)?为什么我们在初始化时放弃了 call-by-need 策略?还有其他情况会发生这种情况吗?请注意,当我们使用 map 而不是像预期的那样手动写下时,不会产生任何输出。

尝试使用 #:: 构造函数

scala> fun(1) #:: fun(2) #:: LazyList.empty
val res0: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>)

#:: 采用名称参数,不像 LazyList.apply 采用值。

而不是使用 LazyList.apply,以下任何工作(不评估他们的论点):

  • LazyList.tabulate(3)(fun)
  • fun(1) #:: fun(2) #:: fun(3) #:: LazyList.empty
  • LazyList.range(1, 4).map(fun)
  1. Why do we want LazyList's arguments like fun(1) to be computed right away? Why do we cast away the call-by-need strategy when initializing? Are there any other cases where such a thing happens? Note that no output is produced when we use map instead of writing this down manually, as expected.

我不认为立即计算 fun(1) 是可取的,但这是因为您使用 LazyList.apply 来构建列表这一事实。 LazyList(fun(1), fun(2), fun(3))LazyList.apply(fun(1), fun(2), fun(3)) 的语法糖,该函数的类型签名是 def apply[A](elems: A*): LazyList[A]。请注意,该函数的参数不是按名称调用的。

那么:为什么 LazyList.apply 没有用按名称调用参数定义?

  1. 在 Scala 中不可能定义带有名字参数的可变参数函数
  2. def apply 实际上来自 SeqFactory ,它混入了大多数集合伴生对象中。 LazyList 伴生对象可能是集合库中伴生对象 apply 没有帮助的地方。
  1. Why are no elements displayed as evaluated? If they are indeed unevaluated, what was this triple "PROCESSING..." thing about? If not, why does println claim so?

LazyList 只知道元素何时被评估,因为您通过在 LazyList 中访问它们来强制它们 。在使用 LazyList.apply 的情况下,会发生以下情况:

  1. 在最初调用 LazyList.apply 时评估参数
  2. LazyList.apply 调用 LazyList.from,它将通过延迟迭代步骤 1
  3. 中实现的中间 Seq 来创建 LazyList

到步骤 2 完成时,LazyList 不知道其内容已被计算。此外,列表的书脊本身是未评估的。

GHCi 中的

:sprint 不是一个很好的比较,因为它在理解事物何时被评估方面更加无所不知。它通过爬取运行时堆并在遇到 thunk 时打印 _ 来实现这一点。相比之下,println 只是调用 LazyList#toString,这是一个常规的 Scala 方法。

我认为您混淆了 2 个问题。元素的计算,并打印它们。举一个更简单的例子,你有:

val lazyList = LazyList(1, 2, 3)

输出:

println(lazyList)

将是:

LazyList(<not computed>)

尽管这些值是明确计算出来的。

在打印惰性列表时,我们实际上是在调用LazyList.toString which calls addStringNoForce. As we can see, as long as the state is not defined, we get the standard LazyList(<not computed>). In order to get the state to be true, we must cause a call to the member state. There are several ways to do that. On way for example is to call head。请注意,这将导致第一个元素被具体化,而不是惰性列表的其余部分。

例如。代码:

val lazyList = LazyList(1, 2, 3)
lazyList.head
println(lazyList)

将输出:

LazyList(1, <not computed>)

以及以下内容:

val lazyList = LazyList(1, 2, 3)
lazyList.tail.head
println(lazyList)

将输出:

LazyList(1, 2, <not computed>)

您看到 PROCESSING... 输出的原因与 LazyList 完全无关。如果你调用 fun(1) 而没有 LazyList,你得到的结果是一样的。