Scala中的不可变数据结构

3
我正在尝试实现一个不可变的数据结构来模拟IT网络和实例(计算机)。这是一个简化版本:
object Sample {

  case class Instance(id: String, flag: Boolean)
  case class Network(id: String, instances: Set[Instance])
  case class Repo(networks: Map[String, Set[Network]])

  // return new Repo with the instance with id == instanceId updated
  // how to do this using lenses?
  def updateInstanceFlag(networksKey: String, instanceId: String, flag: Boolean): Repo = ???
}
updateInstanceFlag 函数应该创建一个更新后的数据副本,对应的实例(具有 id instanceId)被修改。我尝试使用镜头来实现,但代码太复杂了。 特别是,在通过 ID 定位实例或网络并更新数据结构时,我很难进行组合。查询返回可选值也增加了镜头的复杂性。 我使用自己的镜头实现,但没有真正的偏好(我知道 Shapeless、Monocle、Scalaz 的镜头实现)。
我希望听到人们在如何维护“真正”的不可变数据方面的想法和经验。
谢谢。

这对我来说不是很清楚,你能否返回一个新的Repo案例类? - Ende Neu
使用案例类复制来更新嵌套数据结构可能会变得非常复杂,因为您需要在每个级别进行复制 - 在上面的示例中,我需要复制一个实例、包含该实例的网络以及最后的 Repo。数据嵌套越深,情况就会变得更糟。 - yshenhar
现在我明白你的意思了。 - Ende Neu
我可以建议在问题标题前加上“更新嵌套”吗?我认为这将使其意图更加清晰。 - gzm0
1个回答

3

如果我能说服你把Network类中的instances字段也改成一个Map,那么你可以这样合理地做:

object Sample {

  case class Instance(id: String, flag: Boolean)
  case class Network(id: String, instances: Map[String, Instance])
  case class Repo(networks: Map[String, Set[Network]])

  // return new Repo with the instance with id == instanceId updated
  // how to do this using lenses?
  def updateInstanceFlag(repo: Repo, networksKey: String,
      instanceId: String, flag: Boolean): Repo = {

    val nets0 = repo.networks
    val net0 = nets0(networksKey)  // TODO fail gracefully
    val inst0 = net0.instances(instanceId) // TODO fail gracefully
    val inst1 = inst0.copy(flag = flag)
    val net1 = net0 + (instanceId -> inst1)
    val nets1 = nets0 + (networksKey -> net1)

    repo.copy(networks = nets1)
  }

}

由于代码下降和上升的对称性可能已经变得明显,因此将某些部分因子化成一个方法,可以在单个网络上更新实例标志位。

def updateInstanceFlag(net: Network, instanceId: String,
    flag: Boolean): Repo = {

  val inst1 = net.instances(instanceId).copy(flag = flag)
  net.copy(instances = net.instances + (instanceId -> inst1))
}

def updateInstanceFlag(repo: Repo, networksKey: String,
    instanceId: String, flag: Boolean): Repo = {

  val net1 = updateInstanceFlag(repo.networks(networksKey), instanceId, flag)
  repo.copy(networks = repo.networks + (networksKey -> net1))
}

谢谢。更改实例数据结构是可以的,但我认为这种方法在处理复杂和深度嵌套的数据结构时可能也无法很好地扩展。 - yshenhar
不,它不会。但是那真的很难达到。您可能需要考虑为每个级别创建通用的更新函数,并将闭包传递给它们以更新级别。 - gzm0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接