Scala中案例类的不可变成对实例?

Scala中案例类的不可变成对实例?,scala,Scala,我试图建立一种可以逆转的关系模型。例如,北方的反面可能是南方。左的反面可能是右。我想使用case类来表示我的关系。我在这里找到了一个使用case对象的类似解决方案,但它不是我想要的 以下是我的非功能代码: case class Relationship(name: String, opposite:Relationship) def relationshipFactory(nameA:String, nameB:String): Relationship = { lazy val x:Re

我试图建立一种可以逆转的关系模型。例如,北方的反面可能是南方。左的反面可能是右。我想使用case类来表示我的关系。我在这里找到了一个使用case对象的类似解决方案,但它不是我想要的

以下是我的非功能代码:

case class Relationship(name: String, opposite:Relationship)

def relationshipFactory(nameA:String, nameB:String): Relationship = {
  lazy val x:Relationship = Relationship(nameA, Relationship(nameB, x))
  x
}

val ns = relationshipFactory("North", "South")

ns // North

ns.opposite // South

ns.opposite.opposite // North

ns.opposite.opposite.opposite // South
是否可以更改此代码,以便:

  • 它没有崩溃
  • 我可以按需成对制作这些东西

如果您有循环依赖项,这将不起作用。一个选择是:

case class Relationship(name: String)
并有一个setter来指定相反的内容。然后,工厂将做:

def relationshipFactory(nameA:String, nameB:String): Relationship = {
  val x:Relationship = Relationship(nameA)
  val opposite = Relationship(nameB)

  x.setOpposite(opposite)
  opposite.setOpposite(x)
  x
}
另一种选择:

case class Relationship(name: String) {
  lazy val opposite = Utils.computeOpposite(this)
}
在Utils对象上具有相反的逻辑

还有另一种选择:可能您不需要几个
South
实例,所以您应该使用case对象或enum(更多信息请参见)


使用枚举,您可以使用模式匹配来执行该逻辑,而无需任何开销

如果您真的想构建具有循环依赖关系的不可变对象的图,则必须将
反向
声明为
def
,并且(最好)在混合中再抛出一个惰性val:

abstract class Relationship(val name: String) {
  def opposite: Relationship
}

object Relationship {

  /** Factory method */
  def apply(nameA: String, nameB: String): Relationship = {
    lazy val x: Relationship = new Relationship(nameA) {
      lazy val opposite = new Relationship(nameB) {
        def opposite = x
      }
    }

    x
  }

  /** Extractor */
  def unapply(r: Relationship): Option[(String, Relationship)] =
    Some((r.name, r.opposite))

}

val ns = Relationship("North", "South")

println(ns.name)
println(ns.opposite.name)
println(ns.opposite.opposite.name)
println(ns.opposite.opposite.opposite.name)
您可以很快说服自己,如果您在这个循环依赖循环上运行几百万轮,就不会发生什么坏事:

// just to demonstrate that it doesn't blow up in any way if you
// call it hundred million times:
// Should be "North"
println((1 to 100000000).foldLeft(ns)((r, _) => r.opposite).name)
它确实印着“北”。它不适用于case类,但您始终可以添加自己的提取器,因此这是可行的:

val Relationship(x, op) = ns
val Relationship(y, original) = op
println(s"Extracted x = $x y = $y")
它为
x
y
打印“北”和“南”


然而,更明显的做法是只保存关系的两个组件,并添加
contract
作为构造相反对的方法

case class Rel(a: String, b: String) {
  def opposite: Rel = Rel(b, a)
}
实际上,这已经在标准库中实现:

scala> val rel = ("North", "South")
rel: (String, String) = (North,South)

scala> rel.swap
res0: (String, String) = (South,North)

“你有循环依赖,这不起作用”为什么不?完全可以用纯函数的方式创建具有循环依赖关系的对象的复杂图,而不需要任何可变变量或设置器。例如,从解析器组合器构建的解析器就是很好的例子:高度自引用,可以展开到任意深度(至少是堆栈允许的深度),不需要任何与setter的显式连接。