如何创建一对互有引用的不可变Scala实例?
我试图创建一个简单的类,它在一个简单的图中表示一种关系。关系是双向的和可逆的,所以如果A是“高于”B,那么B必须是“低于”A 假设我有两个关系A(上面)和B(下面)的实例,即A.invert==B和B.invert==A。为了使事情变得简单,我会同时从同一个工厂构建A和B 到目前为止,这是我的代码,显然是错误的:如何创建一对互有引用的不可变Scala实例?,scala,Scala,我试图创建一个简单的类,它在一个简单的图中表示一种关系。关系是双向的和可逆的,所以如果A是“高于”B,那么B必须是“低于”A 假设我有两个关系A(上面)和B(下面)的实例,即A.invert==B和B.invert==A。为了使事情变得简单,我会同时从同一个工厂构建A和B 到目前为止,这是我的代码,显然是错误的: case class Relationship(name:String, inverse:Relationship=null) { def setInverse(inverse:R
case class Relationship(name:String, inverse:Relationship=null) {
def setInverse(inverse:Relationship) = {
Relationship(name, inverse)
}
}
val foo = Relationship(name="Foo", Relationship(name="Bar"))
printf(foo.toString) // looks fine
printf(foo.inverse.toString) // null
foo.inverse.inverse = foo // totally wrong!!!
最后一行显然是无效的,因为它试图改变一些不可变的东西。我看到了一种可能有效的方法,但不适用于案例类?如果我想进行循环引用,是否必须放弃案例类
如何创建关系的两个实例,每个实例都有一个名为inverse的属性,该属性是对另一个的引用
编辑0:这是我的尝试2-没有更好
class Relationship(name:String, _inverse:Relationship) {
lazy val inverse = _inverse
}
val foo:Relationship = new Relationship(name="Foo", new Relationship(name="Bar", foo))
printf(foo.toString)
printf(foo.inverse.toString)
当我在scala工作表中尝试这一点时,我得到了一个java.lang.StackOverflowerError,很可能是因为foo.inverse.inverse.inverse序列永远持续。如果不阻止这种无限递归,我就无法知道我是否真的构造了一个对象。你是对的,你不能用case类这样做,因为case类的字段不可避免地类似于
val
s
使用类的一种解决方案是定义关系
,如下所示:
class Relationship(name:String, _inverse: =>Relationship) {
lazy val inverse = _inverse //it could be defined as def as well
} //but good call on lazy evaluation
与之前定义的唯一区别是_inverse是“按名称调用”,也就是说,_inverse
将不会被计算,直到我们真正需要计算它以从中获取值
然后你可以做一些疯狂的事情,比如:
scala> val a: Relationship = new Relationship("foo", new Relationship("bar", a))
a: Relationship = Relationship@695aa1
scala> a.inverse.inverse
res3: Relationship = Relationship@695aa1
甚至
scala> val a: Relationship = new Relationship("foo", a)
a: Relationship = Relationship@14471f25
scala> a.inverse
res6: Relationship = Relationship@14471f25
为下面的第一个问题进行编辑:之所以出现这种魔力,是因为我们同时有一个类关系的lazy val
字段,和,因为关系的构造函数的参数是按名称调用
。
callbyname
参数每次在作为参数的结构体中调用时(就像def一样)都会重新求值。这很好,因为如果你把它放在与惰性val
相等的右边,惰性val
将不会“调用”它右边的任何东西,直到惰性val
本身被调用。所以你是说要反向:
好的inverse
,您以后必须调用\u inverse
,以获取其值,但不是现在,因此在创建关系时,因为在关系体中告诉您在调用inverse
之前,无处求值\u inverse
,在调用inverse
之前,永远不必计算\u inverse
编辑2进行模式匹配:
在我看来,您希望对类关系使用模式匹配,为此,您必须定义一个伴生对象/提取器,如下所示:
object Relationship {
def unapply(r: Relationship): Option[(String, Relationship)] =
Some((r.name, r.inverse))
}
此外,您还必须指定一个val
、lazy val
或def
,该参数引用您为Relationship
指定的name
参数,以便能够通过定义如下关系从外部访问该参数:
class Relationship(val name: String, ...//next part same as before
然后你可以像这样做很好的模式匹配:
scala>val a: Relationship =
new Relationship("foo", new Relationship("bar", a))
a: Relationship = Relationship@31f44709
scala>a match {
| case Relationship("bar", aPrim) if aPrim eq a =>
"this is not the case here"
| case Relationship("foo", Relationship("bar", aPrim)) if aPrim eq a =>
"this is the truth"
| case _ => "wat"
|}
res1: String = this is the truth
关于上述模式匹配的注意事项:注意引用等式测试,它可能与Okay重复,因此神奇之处似乎是表达式_inverse:=>关系中的=>。我对这一点的理解是,你说的_inverse不再是关系的实例,而是一个返回关系实例的函数。但是,当您提供_反向参数时,您只是传递了一个引用(或者它是一个懒惰的引用)。太棒了,谢谢。可爱的绿色勾号和+1。