Scala immutable objects and traits with val fields

后端 未结 5 1331
难免孤独
难免孤独 2021-02-08 03:04

I would like to construct my domain model using immutable objects only. But I also want to use traits with val fields and move some functionality to traits. Please look at the f

相关标签:
5条回答
  • 2021-02-08 03:15

    Here's another solution that, like the OP's code, doesn't work. However, it may provide a simpler (and more generally useful) starting point for extending the language.

    trait Versionable[T] {
       self: { def copy(version: Int): T } =>
       val version = 0
       def incrementVersion = copy(version = version + 1)
    }
    
    case class Customer(name: String, override val version: Int) 
          extends Versionable[Customer] {
       def changeName(newName: String) = copy(name = newName)
    }
    

    The code would work if the compiler recognized the Customer class's copy method as conforming to the method defined in Versionable's self-type annotation, which seems like a natural way to use named and default parameters.

    0 讨论(0)
  • 2021-02-08 03:17

    It's difficult to see how this would work and be consistent with Scala's semantics -- in particular, the semantics of an immutable field defined in a trait. Consider the Versionable trait:

    trait Versionable {
       val version = 0
    }
    

    This declaration says that, unless overridden, the version field will always have the value 0. To change the value of version "without polluting the class constructor with trait fields" (i.e. without explicitly overriding the version field) would violate these semantics.

    0 讨论(0)
  • 2021-02-08 03:33

    Although you said, you don't want to use case classes. Here is a solution using them:

    case class Version(number: Int) {
      override def toString = "v" + number
      def next = copy(number+1)
    }
    
    case class Customer(name: String, version: Version = Version(0)) {
      def changeName(newName: String) = copy(newName)
      def incrementVersion = copy(version = version.next)
    }
    

    Now you can do this:

    scala> val customer = new Customer("Scot")
    customer: Customer = Customer(Scot,v0)
    
    scala> customer.changeName("McDonnald")
    res0: Customer = Customer(McDonnald,v0)
    
    scala> customer.incrementVersion
    res1: Customer = Customer(Scot,v1)
    
    scala> customer // not changed (immutable)
    res2: Customer = Customer(Scot,v0)
    
    0 讨论(0)
  • 2021-02-08 03:33

    This should do what you are looking for:

    trait Request[T <: Request[T]] extends Cloneable {
      this: T =>
      private var rets = 0
      def retries = rets
      def incRetries:T = {
        val x = super.clone().asInstanceOf[T]
        x.rets = rets + 1
        x
      }
    }
    

    Then you can use it like

    case class Download(packageName:String) extends Request[Download]
    val d = Download("Test")
    println(d.retries) //Prints 0
    val d2 = d.incRetries
    println(d2.retries) //Prints 1
    println(d.retries) //Still prints 0   
    
    0 讨论(0)
  • 2021-02-08 03:35

    You cleanest solution is probably to drop some implementation logic from Versionable, and push it down the type stack to a case class (where the copy method will be available to you). Give the version property a default value to complete the design.

    trait Versioned {
      def version : Int
      def nextVersion = version + 1 
    }
    
    case class Customer(name: String, version : Int = 0) extends Versioned {
      def withName(newName: String) = copy(name = newName, version = nextVersion)
    }
    

    If you want, you can also define a type alias somewhere for the version numbering:

    type Version = Int
    val initialVersion = 0
    
    trait Versioned {
      def version : Version
      def nextVersion = version + 1 
    }
    
    case class Customer(name: String, version : Version = initialVersion)
    extends Versioned {
      def withName(newName: String) = copy(name = newName, version = nextVersion)
    }
    
    0 讨论(0)
提交回复
热议问题