如何创建一对互有引用的不可变Scala实例?

如何创建一对互有引用的不可变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

我试图创建一个简单的类,它在一个简单的图中表示一种关系。关系是双向的和可逆的,所以如果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: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。