“稍微概括一下函数常量,返回给定值的无限流.”
def constant[A](a: A): Stream[A]
我的解决方案是:
def constant[A](a: A): Stream[A] = Stream.cons(a,constant(a))
我指的是标准解决方案,它是:
// This is more efficient than `cons(a,constant(a))` since it's just // one object referencing itself. def constant[A](a: A): Stream[A] = { lazy val tail: Stream[A] = Cons(() => a,() => tail) tail }
其中说“效率更高”,见here.
我能知道它为什么效率更高? Streams中的cons构造函数AFAIK已经将头部和尾部标记为懒惰:
def cons[A](hd: => A,tl: => Stream[A]): Stream[A] = { lazy val head = hd lazy val tail = tl Cons(() => head,() => tail) }
为什么我们仍然需要将尾巴标记为懒惰?我不太明白这一点.
解决方法
我认为你错过了优化的根源,即使它已在上面的评论中明确说明.常量中的val尾部是惰性的这一事实是一个实现细节,或者说是使得优化的主要来源成为可能的技巧.优化的主要来源是尾部是自引用的.
暂时让我们摆脱所有懒惰的东西.假设Cons被定义为
case class Cons[+A](h: A,t: () => Stream[A]) extends Stream[A]
让我们将常量定义为
def constant[A](a: A): Stream[A] = { val tailFunc: () => Stream[A] = () => tail val tail: Stream[A] = Cons(a,tailFunc) tail }
是的,这是一个语法上无效的程序,因为tailFunc只在下一行使用tail定义.但想象Scala可以编译它.我想现在很明显,这样的常量实现只会在每次调用时创建一个Cons类实例,无论你多久尝试迭代返回的流,都会使用该实例.
延迟允许的定义尾部只是使代码在逻辑上等同于上面的编译而没有错误.从实现的角度来看,它类似于:
class Lazy[A](var value:A) def constant[A](a: A): Stream[A] = { val lazyTail: Lazy[Stream[A]] = new Lazy(null) // this tailFunc works because lazyTail is already fixed and can be safely // captured although lazyTail.value will be changed val tailFunc: () => Stream[A] = () => lazyTail.value lazyTail.value = new Stream(a,tailFunc) lazyTail.value }
这个代码与许多细节中的实际延迟实现并不完全匹配,因为它实际上很渴望,但我认为它表明你并不需要懒惰来进行优化工作(但是以使用var为代价,Scala编译器无论如何都会这样做在其真实,更复杂的懒惰实现中).
在你天真的实施中
def constant[A](a: A): Stream[A] = Stream.cons(a,constant(a))
懒惰的tail评估允许你不会因堆栈溢出而立即失败,因为从它自身的递归调用常量.但是当你执行常量(无论).tail时,尾部正在被评估,因此再次调用常量方法并创建一个新的Cons对象.每次在新头上调用尾部时都会发生这种情况.
为了再次重新声明它,lazy val tail只是一个允许尾部在创建时引用自身的技巧,而真正重要的部分是tail引用自身的事实,它允许只使用一个对象进行迭代而不是每个对象一个对象下一个.tail电话.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。