无限流:
val ones: Stream[Int] = Stream.cons(1, ones)
如何在自己的声明中使用值?看来这应该会产生编译器错误,但它确实有效。
答案 0 :(得分:9)
查看Stream.cons.apply的签名:
apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]
第二个参数上的⇒
表示它具有按名称调用语义。因此,您的表达式Stream.cons(1, ones)
未经过严格评估;在作为ones
的参数传递之前,不需要计算参数tl
。
答案 1 :(得分:8)
它并不总是一个递归定义。这实际上有效并产生1:
val a : Int = a + 1
println(a)
键入a
时会创建变量val a: Int
,因此您可以在定义中使用它。默认情况下,Int
初始化为0。一个类将为null。
正如@Chris指出的那样,Stream接受=> Stream[A]
所以应用了另一个规则,但我想解释一般情况。这个想法仍然是一样的,但变量是按名称传递的,所以这使得计算递归。鉴于它是通过名称传递的,它是懒惰地执行的。 Stream逐个计算每个元素,因此每次需要下一个元素时都会调用ones
,从而导致同一个元素再次生成。这有效:
val ones: Stream[Int] = Stream.cons(1, ones)
println((ones take 10).toList) // List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
虽然您可以更轻松地创建无限流:Stream.continually(1)
更新为评论中指出的@SethTisue Stream.continually
和Stream.cons
是两种完全不同的方法,结果却截然不同,因为cons
在A
占用continually
时需要=>A
,这意味着continually
每次重新计算元素并将其存储在内存中,cons
除非你将它转换为List
之类的其他结构,否则可以避免存储n次。仅当您需要生成不同的值时,才应使用continually
。有关详细信息和示例,请参阅@SethTisue注释。
但请注意,您需要指定类型,与递归函数相同。
你可以将第一个例子递归:
lazy val b: Int = b + 1
println(b)
这将是stackoverflow。
答案 2 :(得分:3)
这不会产生编译器错误的原因是因为Stream.cons
和Cons
都是non-strict而lazily evaluate是他们的第二个参数。
ones
可以在它自己的定义中使用,因为对象缺点有一个如下定义的apply方法:
/** A stream consisting of a given first element and remaining elements
* @param hd The first element of the result stream
* @param tl The remaining elements of the result stream
*/
def apply[A](hd: A, tl: => Stream[A]) = new Cons(hd, tl)
Cons的定义如下:
final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]
请注意,它的第二个参数tl
是按名称(=> Stream[A]
)而不是按值传递的。换句话说,在函数中使用参数tl
之前,不会对其进行求值。
使用这种技术的一个好处是你可以组成可能只是部分评估的复杂表达。