【问题标题】:What is a proper way to manage flexible, typed, immutable data structures in Scala?在 Scala 中管理灵活、类型化、不可变数据结构的正确方法是什么?
【发布时间】:2009-11-10 20:05:08
【问题描述】:

现在我有这样的课程:

abstract class Record {
  // Required fields
  val productCode:Option[String]
  val price:Option[Double]

  // Optional fields
  val notes:Option[String] = None
  val used:Option[Boolean] = Option(false)
}

然后创建它们:

val r = new Record {
  override val productCode = Option("abc")
  override val price = Option(32.12)
}

需要注意的几点:

  1. 我将 Option 用于非可选字段,以便 一种。我不必记住哪些字段是可选的 湾。我可以在不更改界面的情况下更改哪些字段是可选的
  2. Option 的东西增加了很多噪音。我希望它不存在,但我也不想使用空值。当我使用该结构时,考虑到所有对 getOrElse 的调用时尤其如此。 (我敢打赌,该语言有一种巧妙的方式来声明性地自动装箱。)
  3. 如果子类混合了新字段,例如:

    override val List(productCode, price, discount) = fields // fields is a List

不会编译,因为discount 没有在超类中定义,因此没有覆盖。我不确定是否有办法做到这一点。

我的主要问题是:

  1. 是否有更好的整体方法来管理不可变数据结构?
  2. 是否有一种直接的方法可以复制记录并仅更改一个值而无需编写样板代码?

例如(伪代码}:

val r2 = r.clone { override val used = true }

我听说 2.8 为案例类引入了类似的东西,但是在一种鼓励不可变数据结构的语言中,我会惊讶地发现这在 2.8 之前并不容易。我还在 2.7 中。

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    这似乎是 2.8 中解决的一个问题:

    case class Employee(name: String, age: Int)
    
    val joe = Employee("Joe", 25)
    val bob = joe copy (name = "Bob")
    

    将此与默认值结合起来,您给出的示例可以很容易地重写为案例类,我认为这是实现不可变数据类型的“正确”方式。 (我不确定 scala 是否如此,但来自 ocaml/haskell,这似乎是正确的。)

    在 2.7 中,您将不得不实现一大堆辅助函数:

    def asUsed(r: Record): Record = {
      Record(r.productCode, r.price, r.nodes, Some(true))
    }
    

    哎呀。他们真的应该快点 2.8...

    【讨论】:

      【解决方案2】:

      克隆实例没有简单的方法。 FWIW,不可变数据结构通常很深。例如,List 类只有两个成员:hdtl。列表通过链接成员而增长。

      克隆此类结构,方法是创建最少数量的新数据结构,并尽可能多地引用旧数据结构。通常,这是通过 递归。

      您可以在本书Purely Functional Data Structures 中了解更多信息。本书所依据的paper 是免费提供的。

      您可以在此处查找 Scala 问题,了解处理 Option 数据的有趣方法。很遗憾,对于您的其他问题,我没有任何解决方案。

      【讨论】:

      • 这对 Collection 对象有意义,但对结构没有意义。
      • 嗯,实际上,结构也是如此。不推荐使用大量字段的类。相反,推荐的方法是将其分解为 Value Objects 的聚合。在 Scala 中,我想您可以使用 Trais 来做到这一点,这将使它们成为类的一部分而不是聚合。优点是每个 trait 都可以有自己的克隆方法,从而使样板文件更可重用。
      【解决方案3】:

      Option 用于非可选字段对我来说似乎很疯狂:为什么?

      【讨论】:

      • 我提供了上面的原因。否则,如果我将字段从可选更改为必填。我想我可以在这种情况下使用 Some ,但这并不重要,因为我的意思是要求子类声明抽象 val 有效执行的值。
      • 是的,我同意,我应该在这里使用 Some 这样 None 不是一个有效值。然而,遗憾的是 Some(null) 仍然有效。
      • 即使只是null 也是对Some 类型变量的有效赋值。有一个 NotNull 特征,您可以在一定程度上避免不必要的空值。
      • 这是一个完全正确的观点,但是,我会在评论中问这个问题,而不是在答案中......
      【解决方案4】:

      正如已经说过的,在当前(2.7)Scala 中没有直接的方法可以做到这一点,但从我的角度来看,使用构建器模式可以很容易地做到这一点。演示代码:

      abstract class Record {
        // Required fields
        val productCode:Option[String]
        val price:Option[Double]
      
        // Optional fields
        val notes:Option[String] = None
        val used:Option[Boolean] = Option(false)
      }
      class RecordBuilder{
        private var _productCode:Option[String] = null
        private var _price:Option[Double] = null
      
        // Optional fields
        private var _notes:Option[String] = None
        private var _used:Option[Boolean] = Option(false)
      
        def productCode(in:Option[String]) : RecordBuilder = {
          _productCode = in
          this
        }
        def price(in : Option[Double]) : RecordBuilder = {
          _price = in
          this
        }
        def notes(in:Option[String]) : RecordBuilder = {
          _notes = in
          this
        }
        def used (in : Option[Boolean]) : RecordBuilder = {
          _used = in
          this
        }
      
        def create() : Record  =  {
         val r =  new Record = {
            override productCode = _productCode
            override price = _price
            override notes = _notes
            override used  = _used
      
          }
        r
      }
      }
      object Record{
        def from(in:Record) : RecordBuilder = {
          val r = new RecordBuilder
          r.productCode(in.productCode).price(in.price).notes(in.notes)
          .used(in.used)
        }
      }
      object Test {
        def main(args:Array[String]) = {
          val r = new Record {
          override val productCode = Option("abc")
          override val price = Option(32.12)}
        }
        val r1 = Record.from(r).used(true).create
      }
      

      【讨论】:

      • 这是一种构建不可变结构的方法,以替代在构建时覆盖 val,但不会复制任意列表。想象一下这个有 20 个字段。这是一团乱七八糟的样板代码,让我不敢尝试 Scala。
      • 好吧,我提到这种方式更多地用于复制而不是构造,不幸的是,如果您要创建 Record 的子类,您将需要另一个构建器类,它实际上与实现 clone() 您的子类相同可以克隆的东西你通常必须覆盖克隆()。顺便说一句,你为什么不将强制参数传递给构造函数?像 class Foo(val a:String,val b:String) {}
      【解决方案5】:

      镜头是对不可变数据结构进行操作的绝佳工具。见this question

      【讨论】:

        猜你喜欢
        • 2011-01-14
        • 1970-01-01
        • 2015-12-20
        • 1970-01-01
        • 2014-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-10
        相关资源
        最近更新 更多