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:
All help appreciated. Thanks!!!
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.
You seem to be doing it all wrong. This would be an approach to take in a dynamic language like perl or ruby, but scala isn't like that. You can probably mimic something like that with scala, but it'll be hard enough to make you not want to.
.getDetail
is not something you'll often see in scala. If you want to get a person's name, you'd usually just do person.name
not, person.getDetail(PersonDetail.Name)
. Why? Well, why not? There is simply no reason to do the latter when you can do the former.
Likewise for the setter: person.copy(firstName = "foo") works way better than person.withModiciation(PersonDetail.Name, "foo")
The third case is, perhaps, the most complicated. What if you wanted to apply a whole bunch of modifications? Well, I'll, still argue, that something like
val list = List(
PersonDetail.FirstName -> "foo",
PersonDetail.LastName -> "bar",
PersonDetail.OtherStuff -> "baz"
)
person.withModifications(list)
isn't any better than the scala's customary
person.copy(firstName = "foo", lastName = "bar", otherStuff = "baz")
Have you considered using a Lens library such as Monacle? Lens are functional abstractions that allow you apply functions to part of a data structure, such as your Person case class. A Lens allows you zoom in on part of the structure, so for Person, one could
import monocle.Lens
val firstName = Lens[Person, String]( p => f => p.copy(firstName = f))
val newPerson = firstName.set("someNewName")(person)
In this way each modification in PersonDetail could correspond to a suitable Lens. Lens also support other operations too. For more complex modifications, Lens can be composed as is shown on the Monacle README. Besides calling a set function, they also can modify data based on a current value, to correspond to the FirstNameFirstLetter case.
firstName.headOption.modify(_.toUpper)(person)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With