请考虑以下事项:
case class Node(var left: Option[Node], var right: Option[Node])
很容易看出你如何遍历这个,搜索它,无论如何。但现在想象你这样做了:
val root = Node(None, None)
root.left = root
现在,这很糟糕,灾难性的。事实上,你把它输入到一个REPL中,你会得到一个StackOverflow(嘿,这对于一个乐队来说是一个好名字!)和堆栈跟踪一千行。如果您想尝试,请执行以下操作:
{ root.left = root }: Unit
压制REPL善意打印结果的尝试。
但要构建它,我必须专门给出案例级可变成员,这是我在现实生活中永远不会做的事情。如果我使用普通的可变成员,我会遇到构造问题。我能来的最近的是
case class Node(left: Option[Node], right: Option[Node])
val root: Node = Node(Some(loop), None)
然后root
具有相当丑陋的值Node(Some(null),None)
,但它仍然不是循环的。
所以我的问题是,如果数据结构是传递不可变的(也就是说,它的所有成员都是不可变的值或对其他数据结构的引用本身是不可变的),它是否保证是非循环的?< / p>
如果是的话会很酷。
答案 0 :(得分:4)
是的,有可能创建循环数据结构,即使是纯粹的,不可改变的,透明的,无效的语言中的纯不可变数据结构。
&#34;显而易见&#34;解决方案是将潜在的循环引用拉出到单独的数据结构中。例如,如果您将图表表示为邻接矩阵,那么您在数据结构中不需要循环来表示图形中的循环。但这是作弊:每个问题都可以通过添加一个间接层来解决(除了有太多间接层的问题)。
另一种欺骗行为是绕过Scala的外部不变性保证,例如:使用Java反射方法在默认的Scala-JVM实现上。
可以创建实际的循环引用。这项技术被称为Tying the Knot,它依赖于懒惰:你实际上可以设置对你尚未创建的对象的引用,因为引用将被懒惰地评估,到那时对象将是创建。 Scala支持各种形式的懒惰:lazy val
s,按名称参数,现在已弃用DelayedInit
。此外,你可以&#34;假&#34;使用函数或方法的懒惰:在生成该东西的函数或方法中包装您想要延迟的东西,并且在调用函数或方法之前不会创建它。
因此,在Scala中也应该使用相同的技术。
答案 1 :(得分:1)
如何将lazy
与call by name
一起使用?
scala> class Node(l: => Node, r: => Node, v: Int)
// defined class Node
scala> lazy val root: Node = new Node(root, root, 5)
// root: Node = <lazy>