在其自己的定义中使用的变量?
A variable used in its own definition?
无限流:
val ones: Stream[Int] = Stream.cons(1, ones)
一个值怎么可能在它自己的声明中使用?看起来这应该会产生编译器错误,但它确实有效。
看Stream.cons.apply的签名:
apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]
第二个参数的 ⇒
表示它具有按名称调用的语义。因此,您的表达式 Stream.cons(1, ones)
没有得到严格评估;在作为 tl
.
的参数传递之前,不需要计算参数 ones
并不总是递归定义。这实际上有效并产生 1:
val a : Int = a + 1
println(a)
变量a
是在你键入val a: Int
时创建的,所以你可以在定义中使用它。 Int
默认初始化为 0。 class 将为空。
正如@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
可以避免存储它n次,除非你将它转换为其他结构,如List
。仅当需要生成不同的值时才应使用 continually
。有关详细信息和示例,请参阅@SethTisue 评论。
但请注意,您需要指定类型,与递归函数相同。
您可以使第一个示例递归:
lazy val b: Int = b + 1
println(b)
这会导致Whosebug。
这不会产生编译器错误的原因是因为 Stream.cons
和 Cons
都是 non-strict and lazily evaluate 它们的第二个参数。
ones
可以在它自己的定义中使用,因为对象 cons 有一个应用方法定义如下:
/** 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)
而缺点是这样定义的:
final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]
请注意,它的第二个参数 tl
是按名称 (=> Stream[A]
) 而不是按值传递的。换句话说,参数 tl
在函数中使用之前不会计算。
使用此技术的一个优点是您可以编写可能仅部分计算的复杂表达式。
无限流:
val ones: Stream[Int] = Stream.cons(1, ones)
一个值怎么可能在它自己的声明中使用?看起来这应该会产生编译器错误,但它确实有效。
看Stream.cons.apply的签名:
apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]
第二个参数的 ⇒
表示它具有按名称调用的语义。因此,您的表达式 Stream.cons(1, ones)
没有得到严格评估;在作为 tl
.
ones
并不总是递归定义。这实际上有效并产生 1:
val a : Int = a + 1
println(a)
变量a
是在你键入val a: Int
时创建的,所以你可以在定义中使用它。 Int
默认初始化为 0。 class 将为空。
正如@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
可以避免存储它n次,除非你将它转换为其他结构,如List
。仅当需要生成不同的值时才应使用 continually
。有关详细信息和示例,请参阅@SethTisue 评论。
但请注意,您需要指定类型,与递归函数相同。
您可以使第一个示例递归:
lazy val b: Int = b + 1
println(b)
这会导致Whosebug。
这不会产生编译器错误的原因是因为 Stream.cons
和 Cons
都是 non-strict and lazily evaluate 它们的第二个参数。
ones
可以在它自己的定义中使用,因为对象 cons 有一个应用方法定义如下:
/** 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)
而缺点是这样定义的:
final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A]
请注意,它的第二个参数 tl
是按名称 (=> Stream[A]
) 而不是按值传递的。换句话说,参数 tl
在函数中使用之前不会计算。
使用此技术的一个优点是您可以编写可能仅部分计算的复杂表达式。