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

时间:2016-05-23 22:17:18

标签: scala

我正在尝试创建一个简单的类,它在一个简单的图形中表示一种关系。关系是双向的和可逆的,所以如果A在“B”之上,则B必须“低于”A。

假设我有两个关系A(上面)和B(下面)的实例,A.invert == B和B.invert == A.为了简单起见,我将从A和B构建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!!!

最后一行显然无效,因为它试图改变一些不可变的东西。我看到一种可能有效的方法here,但不是用于案例类?如果我想做一个循环引用,我是否必须放弃案例类?

如何创建两个Relationship实例,每个实例都有一个名为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.StackOverflowError,很可能是因为序列foo.inverse.inverse.inverse永远存在。我无法知道我是否真的在不阻止这种无限递归的情况下构造了一个对象。

1 个答案:

答案 0 :(得分:2)

你是对的,因为案例类的字段不可避免地像val一样,你不能用案例类做到这一点。

使用类的一个解决方案是定义Relationship,如下所示:

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字段,,因为参数为Relationship的构造函数是call-by-name。 每次重新评估一个call-by-name参数(就像def一样),它在结构体中被调用,它是参数。现在这很好,因为如果你把它放在与lazy val平等的右侧,那么lazy val就会赢得""电话"右边的任何东西,直到lazy val本身被称为。所以你要反过来说: 好的inverse,你将来必须在_inverse之后调用它来获取它的价值而不是现在,因此在{{>创建的关系,因为在{{{{ 1}}在您致电Relationship之前,您已经告知无法评估_inverse,在致电inverse之前,您永远不必评估_inverse

用于模式匹配的

编辑2 : 在我看来,你想使用模式匹配你的类关系,这样你就必须定义一个伴侣对象/提取器,如下所示:

inverse

您还必须指定object Relationship { def unapply(r: Relationship): Option[(String, Relationship)] = Some((r.name, r.inverse)) } vallazy val,以引用您def能够提供给name的{​​{1}}参数通过定义关系来从外部访问它,例如:

Relationship

那么你可以像这样做很好的模式匹配:

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

的参考相等性测试