How do you mix functionality with each step of an iterative procedure using Scala?
I am working on an optimization routine in Scala and am looking for advice on how to structure my problem. The procedure takes one step at a time, so I naively modeled the problem using the class Step
:
class Step(val state:State) {
def doSomething = ...
def doSomethingElse = ...
def next:Step = ... // produces the next step in the procedure
}
Each step in the procedure is represented by an immutable class Step
whose constructor is set to the state generated by the previous step and the method next
creates the next instance Step
. The basic idea is to wrap this with a Iterator[Step]
so that the steps can be taken until the optimization converges. While this is a bit oversimplified, it works well for the vanilla case.
Now, however, I need to add various extensions to the algorithm, and I need to randomly mix these extensions depending on the problem being optimized. This is usually done with a stacked pattern, but this approach poses challenges for this problem. Below is an example of two potential extensions:
trait FeatureA extends Step {
// Extension-specific state passed from step to step
val aState:FeatureAState = ...
// Wrap base methods to extend functionality
abstract override def doSomething = { ...; super.doSomething(); ... }
}
// Just like Feature A
trait FeatureB extends Step {
val bState:FeatureBState = ...
abstract override def doSomething = { ...; super.doSomething(); ... }
}
Sometimes optimization will require FeatureA
, and sometimes FeatureB
, sometimes both.
The main problem is that the next
base class method does not know which extensions were mixed, so subsequently created stages will not contain any extensions mixed with the original ones.
In addition, each extension must pass its own state from step to step. In this example, instances of FeatureAState
/ FeatureBState
are included in their respective traits, but without method overriding next
FeatureA
and FeatureB
no way to traverse their unique state. Redefinition next
in every feature is impossible, since there can be a combination of these extensions mixed inside, and each of them knows only about itself.
So, I seem to have drawn myself in a corner and hope someone has an idea of how to approach this with Scala. What's the best design pattern for this type of problem?
source to share
You may be interested in exploring the F-bound polymorphism pattern . This template allows you to define methods that return the current subtype in a trait or base class. Here's a simplified version of your example:
trait Step[T <: Step[T]] { self: T =>
val name: String
def next: T
}
case class BasicStep(name: String) extends Step[BasicStep] {
def next = this.copy(name = name + "I")
}
case class AdvancedStep(baseName: String, iteration: Int) extends Step[AdvancedStep] {
val name = s"$baseName($iteration)"
def advancedFunction = println("foobar")
def next = this.copy(iteration = iteration + 1)
}
So, we have defined a base trait Step
that has a method name
and next
that returns everything of type self from the expanding class. For example, method next
in BasicStep
returns a BasicStep
. This allows us to iterate over and use overridden subtypes as desired:
val basicSteps = Iterator.iterate(BasicStep("basic"))(_.next).take(3).toList
//basicSteps: List[BasicStep] = List(BasicStep(basic), BasicStep(basicI), BasicStep(basicII))
val advancedSteps = Iterator.iterate(AdvancedStep("advanced", 0))(_.next).take(3).toList
//advancedSteps: List[AdvancedStep] = List(AdvancedStep(advanced,0), AdvancedStep(advanced,1), AdvancedStep(advanced,2))
val names = advancedSteps.map(_.name)
//names: List[String] = List(advanced(0), advanced(1), advanced(2))
advancedSteps.last.advancedFunction
//foobar
If you want to mix several types like this, you unfortunately cannot use generics (you will get the error "inherits different types of trait instances"). However, you can use abstract type members to express F-linked polymorphism:
trait Step { self =>
type Self <: Step { type Self = self.Self }
val name: String
def next: Self
}
trait Foo extends Step {
val fooMarker = "foo"
}
trait Bar extends Step {
val barMarker = "bar"
}
case class FooBar(name: String) extends Foo with Bar {
override type Self = FooBar
def next = this.copy(name + "I")
}
Then the instances FooBar
will have methods on Foo
and on Bar
:
val fooBar = FooBar("foobar").next.next
fooBar.barMarker //"bar"
fooBar.fooMarker //"foo"
fooBar.name //"fooNameII"
Note that the name comes from Foo
as it was first mixed.
source to share