score:2

Accepted answer

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.

score:2

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")

score:2

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)

Related Query

More Query from same tag