Scala中的Case类的不可变配对实例?(Immutable paired instances of Case Classes in Scala?)

我试图建立一种可以颠倒的关系。 例如,北方的相反可能是南方。 左派的反面可能是对的。 我想用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

可以更改此代码,以便:

它不会崩溃 我可以按需求创建这些东西。

I'm trying to model a relationship which can be reversed. For example, the reverse of North might be South. The reverse of Left might be Right. I'd like to use a case class to represent my relationships. I found a similar solution that uses case Objects here, but it's not quite what I want, here.

Here's my non-functional code:

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

Can this code be changed so that:

It dosen't crash I can create these things on demand as pairs.

最满意答案

如果你真的想用循环依赖来构建不可变对象的图形,你必须声明为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)

它确实打印“北”。 它不适用于案例类,但您可以随时添加自己的提取器,因此这样可行:

val Relationship(x, op) = ns val Relationship(y, original) = op println(s"Extracted x = $x y = $y")

它为x和y打印“北”和“南”。


但是,更明显的做法是仅保存关系的两个组件,并添加opposite的方法来构造相反的对。

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)

If you really want to build graphs of immutable objects with circular dependencies, you have to declare opposite as def, and (preferably) throw one more lazy val into the mix:

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)

You can quickly convince yourself that nothing bad happens if you run a few million rounds on this circle of circular dependencies:

// 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)

It indeed prints "North". It doesn work with case classes, but you can always add your own extractors, so this works:

val Relationship(x, op) = ns val Relationship(y, original) = op println(s"Extracted x = $x y = $y")

It prints "North" and "South" for x and y.


However, the more obvious thing to do would be to just save both components of a relation, and add opposite as a method that constructs the opposite pair.

case class Rel(a: String, b: String) { def opposite: Rel = Rel(b, a) }

Actually, this is already implemented in the standard library:

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

更多推荐