Reputation: 53
I'm designing a simple data-processing pipeline in Scala. It involves PipelineStage
's, which transform
some StageOutput
into another StageOutput
. A Pipeline
is a wrapper for a sequence of PipelineStage
's which needs to generically access all of their transform
methods.
However I'm having trouble as both solutions I've come up with fundamentally don't work... the first relies on an abstract polymorphic method being implemented in a non-polymorphic way (doesn't compile) and the second relies on being able to use a Seq[AbstractTrait]
where the AbstractTrait
is polymorphic, which is again meaningless to the compiler. See as follows...
Scenario 1. Make the transform
method polymorphic.
abstract trait PipelineStage {
def transform[A <: StageOutput, B <: StageOutput](in: A): B
}
class PipelineStage2 extends PipelineStage {
def transform(in: StageOutput1): StageOutput2
}
class Pipeline {
def stages: Seq[PipelineStage]
}
Here the Pipeline
has no trouble compiling, but the stages won't compile since the signatures of their transform
methods, whilst they 'respect' the abstract signature's polymorphism, aren't actually polymorphic themselves so don't match up as far as the compiler is concerned.
Scenario 2. Make the PipelineStage
trait itself polymorphic.
abstract trait PipelineStage[A <: StageOutput, B <: StageOutput] {
def transform(in: A): B
}
class PipelineStage2 extends PipelineStage[StageOutput1, StageOutput2] {
def transform(in: StageOutput1): StageOutput2
}
class Pipeline {
def stages: Seq[PipelineStage]
}
This solves the problem for the PipelineStage
s, they have no trouble compiling, and their transform methods work fine on their own. However, they don't technically implement the same trait now, so Pipeline
doesn't compile since Seq[PipelineStage]
is meaningless now...
Is there an established pattern for either implementing polymorphic abstract methods without the polymorphism, or referencing a sequence of classes implementing the same abstract polymorphic trait? My feeling is no and that I've probably approached this in the wrong way, but perhaps there's a syntactical trick I'm missing somewhere along the line. Thanks.
Upvotes: 2
Views: 145
Reputation: 170723
The first one doesn't do what you want at all: StageOutput1
and 2
in def transform[StageOutput1, StageOutput2](in: StageOutput1): StageOutput2
are just parameter names, it means exactly the same as def transform[A, B](in: A): B
.
My suggestion would be to use the second, but instead of class Pipeline
you can add
def compose[C <: StageOutput](other: PipelineStage[B, C]): PipelineStage[A, C] = new PipelineStage[A, C] {
def transform(in: A) = other.transform(PipelineStage.this.transform(in))
}
and build up the pipeline you want, provided the types actually match.
(Also, your PipelineStage
is basically a Function1
with a constraint; if you don't plan to add other methods, or to intentionally limit the kinds of stages that can be written, you may want to just use functions and take advantage of the standard library!)
Upvotes: 4