Scala case constructor with dynamic fields

I have an immutable State , and I'm processing a message queue, where each message is a list of new values ​​for some fields in state.

New values ​​may relate to part of the field — for example, setting or clearing one flag (bit) from many, or changing only the 8-bit part with a low or high level of a 16-bit field.

After processing the message, I want to get an immutable copy of the state with the changes made.

 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 create a field receiver based on a "field key"?

Q # 2 - what is the best way to make a field installer based on a "field key"?

Q # 3 - what's the best way to create a new object, given the message of variability?

Note:

  • I am familiar with Java, but just starting with Scala.
  • I am doing a project with a clean sheet, so you can comment in more detail on any part of the code, everything can be rewritten.
  • The above code works, but it feels too far from what Scala should look like.
  • I do not want to use reflection as suggested here: Scala copy of case class with dynamic named parameter
  • I do not want to use a key-value map of values ​​instead of the fields of a real class like Python.

All help is appreciated. Thanks!!!

+5
source share
3 answers

Lenses formalize the concept of having source functions and non-mutating “setters” for data structures, but we don't need a formalism to get some of the benefits. We will use control inversion to create modifier functions for your class. These modifier functions will code ideas such as:

  • 'Give me the current state and the new AX, and I will give you a new state.

  • 'Give me the current state and the function that calculates the new AX from the current AX, and I will give you a new state

  • 'Give me the current state and 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 } } 

PS, perhaps you can simplify some of your types, for example. StateField does not have to be a multi-level hierarchy - you can split it into separate Register and Flag enumerations; and you can expand or enter the ModificationMessage alias, as this is just a list.

+1
source

You seem to be doing it all wrong. It would be an approach to a dynamic language like perl or ruby, but scala is not like it. You might be able to imitate something like scala, but it will be tricky enough for you not to.

.getDetail is not what you often see in scala. If you want to get the name of a person, you are usually just person.name not, person.getDetail(PersonDetail.Name) . What for? Why not? There is simply no reason to do the last when you can do the first.

Similarly for setter: person.copy (firstName = "foo") works better than person.withModiciation(PersonDetail.Name, "foo")

The third case is perhaps the most difficult. What if you want to apply a whole bunch of modifications? Well, I’m still arguing, something like

 val list = List( PersonDetail.FirstName -> "foo", PersonDetail.LastName -> "bar", PersonDetail.OtherStuff -> "baz" ) person.withModifications(list) 

no better than regular scala

 person.copy(firstName = "foo", lastName = "bar", otherStuff = "baz") 
+2
source

Have you considered using the Lens library like Monacle ? Lenses are functional abstractions that allow you to apply functions to part of the data structure, for example, to the Person Person class. The lens allows you to increase part of the structure, so for the face you can

 import monocle.Lens val firstName = Lens[Person, String]( p => f => p.copy(firstName = f)) val newPerson = firstName.set("someNewName")(person) 

Thus, each modification of PersonDetail can correspond to a suitable lens. The lens also supports other operations. For more complex modifications, the lens can be designed as shown on the Monacle README. In addition to calling the set function, they can also modify data based on the current value to match the FirstNameFirstLetter case.

 firstName.headOption.modify(_.toUpper)(person) 
+2
source

Source: https://habr.com/ru/post/1259012/


All Articles