11

无限流:它自己的定义中使用的变量?

val ones: Stream[Int] = Stream.cons(1, ones)

它怎么可能在自己的声明中使用的值?看起来这应该会产生一个编译器错误,但它工作。

+2

递归函数的工作方式相同。认为'ones'是一个零参数函数。 –

回答

8

这并不总是一个递归定义。这实际工作,并产生1:

val a : Int = a + 1 
println(a) 

变量a当你输入val a: Int创建,这样你就可以在定义中使用它。默认情况下,Int初始化为0。一个类将是空的。

由于@克里斯指出,流接受=> Stream[A]所以有点另一规则适用的,但我想解释一般情况。这个想法仍然是一样的,但变量是通过名称传递的,所以这使得计算递归。鉴于它是通过名称传递的,它会被懒惰地执行。流计算每个元件一个接一个,所以它调用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.continuallyStream.cons是两个完全不同的方法,具有非常不同的结果,因为cons需要Acontinually需要=>A,这意味着continually每次重新计算在存储器中的元件并且将其存储,当cons可避免其存储n次除非将其转换为其他结构等List。只有在需要生成不同的值时,才应使用continually。有关详细信息和示例,请参阅@SethTisue注释。

但是请注意,您必须指定类型,同为递归函数。

而且可以使第一个例子递归:

lazy val b: Int = b + 1 
println(b) 

这将计算器。

+2

只需要注意,'Stream.cons(1,ones)'是一个使用有限内存的循环结构,'Stream.continually(1)'是一个使用潜在无限内存的线性结构。 –

+1

@SethTisue你能解释一下吗?有没有实际的区别?不知道你的意思。如果你做'Stream.continually(1).take(10000000)'它仍然不会分配所有这些数字 – Archeg

+2

只使用几个字的内存和一些可能消耗整个内存的东西之间存在巨大的实际区别堆。 (但是,请注意,一旦你调用'take',你最终会得到同样的结构,所以它的重要性取决于你实际上对流所做的事情。)更多内容:https://gist.github.com/ SethTisue/ce598578874accba98c0,https://groups.google.com/d/msg/scala-user/3yypUKJBP04/Q_bowgIry44J –

9

看的Stream.cons.apply签名:

apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]

上的第二个参数的表明它具有调用 - 名语义。因此,您的表达Stream.cons(1, ones)没有严格评估;参数ones不需要被以作为tl参数被传递之前计算的。

3

这不会产生编译器错误的原因是因为Stream.consCons都是non-strictlazily evaluate它们的第二个参数。

ones可以在自己的定义中使用,因为对象利弊有这样定义的应用方法:

/** 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只有在函数中使用时才会被评估。

使用这种技术的一个优点是您可以撰写复杂的表达式,可能只能部分评估