Scala case class copy constructor with dynamic fields
case-class, getter, immutability, scala, setter
Solution
Lenses formalise the concept of having getter and non-mutating 'setter' functions for data structures, but we don't need the formalism to gain some of the advantages. We will use inversion of control to create modifier functions for your class. These modifier functions will encode ideas like:
'Give me the current state and a new AX, and I will give you a new state'
'Give me the current state and a function that calculates a new AX from the current AX, and I will give you a new state'
'Give me the current state and the new flags, and I will give you a new state'
So, the code:
case class State(ax: Int, flags: Int) {
private def setAx(newAx: Int): State = copy(ax = newAx)
private def modAx(f: Int => Int): State = setAx(f(ax))
private def setFlags(newFlags: Int): State = copy(flags = newFlags)
def withModification(modification: StateFieldModification): State =
modification.field match {
case StateField.AX => setAx(modification.value)
case StateField.AH =>
modAx { ax => (ax & 0x00FF) | (modification.value << 8) }
case StateField.AL =>
modAx { ax => (ax & 0xFF00) | modification.value }
case StateField.CF =>
setFlags(flagsWithBit(1, modification.value > 0))
case StateField.CF =>
setFlags(flagsWithBit(12, modification.value > 0))
}
def withModifications(message: ModificationMessage): State =
message.content.foldLeft(this) { (state, modification) =>
state withModification modification
}
}
P.S., you can probably simplify some of your types, e.g. `StateField` doesn't really need to be a multi-level hierarchy--you can split it up into separate `Register` and `Flag` enums; and you can unwrap or type alias `ModificationMessage` since it's just a list.
Problem
I have an immutable `State`, and I process a message queue, where each message is a list of new values to some fields in the state. The new values may apply to a part of a field - for example setting or clearing one flag (bit) out of many, or changing just the low or high 8-bit part of a 16-bit field. After processing the message, I want to get an immutable copy of the state, with the modifications applied. ``` object StateField { sealed abstract class StateField() sealed abstract class Register extends StateField sealed abstract class Flag extends StateField case object AX extends Register case object AH extends Register case object AL extends Register case object CF extends Flag case object OF extends Flag } class StateFieldModification(field: StateField, value: Int) class ModificationMessage(content: List[StateFieldModification]) case class State(AX: Int, Flags: Int) { def readRegister(field: StateField.Register): Int = field match { case StateField.AX => this.AX case StateField.AH => this.AX & 0xFF case StateField.AL => (this.AX << 8) & 0xFF } def readFlag(field: StateField.Flag): Boolean = field match { case StateField.CF => (this.Flags & 0x0001) != 0 case StateField.OF => (this.Flags & 0x0800) != 0 } def flagsWithBit(flagBit: Int, newBitValue: Boolean): Int = { if (newBitValue) Flags | (1 << flagBit) else Flags & ~(1 << flagBit) } def withModification(modification: StateFieldModification): State = modification.field match { case StateField.AX => this.copy(AX = modification.value) case StateField.AH => this.copy(AX = (this.AX & 0x00FF) | (modification.value << 8)) case StateField.AL => this.copy(AX = (this.AX & 0xFF00) | modification.value) case StateField.CF => this.copy(Flags = flagsWithBit(1, modification.value > 0)) case StateField.CF => this.copy(Flags = flagsWithBit(12, modification.value > 0)) } def withModifications(message: ModificationMessage) = ??? } ``` Q#1 - what's the best way to make a field getter based on a "field key"? Q#2 - what's the best way to make a field setter based on a "field key"? Q#3 - what's the best way to make a new object, given a mutability message? Please note that: - I'm experienced with Java but just starting with Scala. - I'm doing a clean-sheet project, so you're more than welcome to comment about any part of the code, anything can be rewritten. - The code above works, but it feels too far from what Scala elegance should look like. - I don't want to use reflection, as suggested here: Scala case class copy with dynamic named parameter - I don't want to use key-value map of fields instead of real class fields, like Python does. All help appreciated. Thanks!!!