I am trying to model a scenario where I'm trying to achieve immutability rather than modifying instance variables. The way I'm achieving this is as below.
case class TSS(k:Int, v:Int)
case class Combiner(a:Int,b:Int, tss: TSS) {
def func1():Option[TSS] = None
def func2():Option[TSS] = None
def modifyState(externalV: Int): Combiner = {
Combiner(this.a 1, this.b 1, func1())
}
}
case class OneCombiner extends Combiner {
override def func1():Option[TSS] = SOME_MODIFIED_TSS
override def func2():Option[TSS] = SOME_MODIFIED_TSS
}
case class TwoCombiner extends Combiner {
override def func1():Option[TSS] = SOME_MODIFIED_TSS
override def func2():Option[TSS] = SOME_MODIFIED_TSS
}
Now when I'm trying to use the OneCombiner
and TwoCombiner
as below they are using the func1
and func2
of base class and setting the value of tss
to None
rather than using the overridden versions.
For example
val a:Combiner = OneCombiner
val anotherCombiner = a.modifyState()
In this case anotherCombiner.tss
is being returned as None
rather than SOME_TSS_VALUE
.
What is the correct functional way of modelling such behaviour in Scala ?
I also tried making Combiner
abstract case class
but that failed to compile because then I was unable to create the Combiner
object in modifyState
method.
CodePudding user response:
The problem is that modifyState
is creating a new Combiner
rather than the appropriate subclass. This is because it is combining state with abstract behaviour in one class.
The best solution is to make a separate concrete State
class and have the Combiner
s as functions that take a State
and return a new one. This separates the different behaviours from the actual state itself.
case class TSS(k: Int, v: Int)
case class State(a: Int, b: Int, tss: TSS)
trait Combiner {
def func1(): TSS
def modifyState(state: State, externalV: Int): State
= State(state.a 1, state.b 1, func1())
}
case class OneCombiner() extends Combiner {
def func1(): TSS = ???
}
case class TwoCombiner() extends Combiner {
def func1(): TSS = ???
}
If you want to retain a single class you need to have a separate constructor for each subclass, and the easiest way to do that is to make modifyState
virtual and implement it in each subclass. It is not clear how many of the other values need to be exposed in the trait
so I have put them all in, just in case.
case class TSS(k: Int, v: Int)
trait Combiner {
def a: Int
def b: Int
def tss: TSS
def func1(): Option[TSS]
def func2(): Option[TSS]
def modifyState(externalV: Int): Combiner
}
case class OneCombiner(a: Int, b: Int, tss: TSS) extends Combiner {
override def func1(): Option[TSS] = ???
override def func2(): Option[TSS] = ???
def modifyState(externalV: Int): OneCombiner =
OneCombiner(this.a 1, this.b 1, func1().getOrElse(???))
}
case class TwoCombiner(a: Int, b: Int, tss: TSS) extends Combiner {
override def func1(): Option[TSS] = ???
override def func2(): Option[TSS] = ???
def modifyState(externalV: Int): TwoCombiner =
TwoCombiner(this.a 1, this.b 1, func1().getOrElse(???))
}