I am trying to implement a kind of simple processing pipeline in Go, where each processor has a determined input and output type, and a list of successor processors that take current processor output type as input, and may have successor processors of their own.
I am running into issues on how to add successor processors to the current one, regardless of their output type. I tried using any as a wildcard type like I would do with ? in Java, but Go is not having it.
What I have in Go is this:
type Processor[InputType any, OutputType any] struct {
nextProcessors []*Processor[OutputType, any]
ProcessingFunction func(InputType) OutputType
}
func (b *Processor[InputType, OutputType]) Process(input InputType) {
result := b.ProcessingFunction(input)
for _, nextProcessor := range b.nextProcessors {
nextProcessor.Process(result)
}
}
func (b *Processor[InputType, OutputType]) AddOutputProcessor(p *Processor[OutputType, any]) {
b.nextProcessors = append(b.nextProcessors, p)
}
func main() {
outputer := Processor[int, string]{ProcessingFunction: func(input int) string {
print(input)
return string(input)
}}
doubler := Processor[int, int]{ProcessingFunction: func(input int) int { return input * 2 }}
rng := Processor[int, int]{ProcessingFunction: func(input int) int { return rand.Intn(input) }}
rng.AddOutputProcessor(&doubler)
doubler.AddOutputProcessor(&outputer)
rng.Process(20)
}
Which gives me a compilation error:
cannot use &doubler (value of type *Processor[int, int]) as type *Processor[int, any]
Is there a way to ignore the output type of the successor processor? Or should I maybe go a different way about it? I would just like to make sure that successor processors can accept the right type of input.
For reference, here is the interface definition in Java that works the way I intend it to.
public interface Processor<InputType, OutputType> {
void addOutputProcessor(Processor<OutputType, ?> outputProcessor);
void process(InputType input);
}
public class Pipeline {
private abstract class BaseProcessor<InputType, OutputType> implements Processor<InputType, OutputType> {
List<Processor<OutputType, ?>> nextProcessors = new ArrayList<>();
abstract OutputType processHelper(InputType input);
@Override
public void addOutputProcessor(Processor<OutputType, ?> outputProcessor) {
nextProcessors.add(outputProcessor);
}
@Override
public void process(InputType input) {
OutputType result = processHelper(input);
for (Processor<OutputType, ?> nextProcessor : nextProcessors) {
nextProcessor.process(result);
}
}
}
private class RandomNumberGenerator extends BaseProcessor<Integer, Integer> {
@Override
Integer processHelper(Integer input) {
int generatedNumber = new Random().nextInt(input);
return generatedNumber;
}
}
private class IncrementProcessor extends BaseProcessor<Integer, Integer> {
@Override
Integer processHelper(Integer input) {
return input + 1;
}
}
private class DoubleProcessor extends BaseProcessor<Integer, Integer> {
@Override
Integer processHelper(Integer input) {
return input * 2;
}
}
private class Outputer extends BaseProcessor<Integer, String> {
String name;
public Outputer(String name) {
this.name = name;
}
@Override
String processHelper(Integer input) {
String output = String.format("Pipeline %s result: %d", name, input);
System.out.println(output);
return output;
}
}
public void buildAndRunPipeline() {
Processor<Integer, String> doublingOutputter = new Outputer("Doubling");
Processor<Integer, String> incrementingOutputter = new Outputer("Incrementing");
Processor<Integer, String> rngOutputter = new Outputer("Generating number");
Processor<Integer, Integer> doubler = new DoubleProcessor();
doubler.addOutputProcessor(doublingOutputter);
Processor<Integer, Integer> incrementer = new IncrementProcessor();
incrementer.addOutputProcessor(incrementingOutputter);
Processor<Integer, Integer> starter = new RandomNumberGenerator();
starter.addOutputProcessor(rngOutputter);
starter.addOutputProcessor(doubler);
starter.addOutputProcessor(incrementer);
starter.process(20);
}
public static void main(String[] args) {
Pipeline p = new Pipeline();
p.buildAndRunPipeline();
}
}