使用GO GENRICS实现一系列处理器

发布于 2025-01-24 13:49:57 字数 4476 浏览 2 评论 0原文

我正在尝试在GO中实现一种简单的处理管道,每个处理器都有确定的输入和输出类型,以及将当前处理器输出类型作为输入的后继处理器列表,并且可能具有其自己的后继处理器。

我正在遇到有关如何将后继处理器添加到当前一个问题的问题,无论其输出类型如何。我尝试使用作为通配符类型,就像我在java中使用一样,但是Go没有它。

我走的是:

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

这给了我汇编错误:

无法使用& doubleer(类型 *处理器[int,int])作为类型 *处理器[int,任何]

是否可以忽略后继处理器的输出类型?还是我应该采取不同的方式?我只想确保后继处理器可以接受正确的输入类型。

作为参考,这是Java中的接口定义,它按照我打算的方式进行操作。

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();
    }
}

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();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

逆夏时光 2025-01-31 13:49:57

有没有办法忽略后继处理器的输出类型?

。因此,*处理器[int,任何]*处理器[int,int]的类型不同,并且您不能将一个分配给另一个类型您的错误消息,

要构建一个长的链条,您需要参数process方法本身,但这在GO 1.18中不可能,即使您这样做,您也会在不知道下一个处理器的输出类型的同一问题中继续存在

例如在Haskell中实现

我相信,您可以在没有反射的情况下获得的最接近, 类型处理器是多余的,但是将其更靠近您的代码):

package main

import (
    "fmt"
    "strconv"
)

type Processor[In, Out any] func(In) Out

func Process[In, Out any](input In, processor Processor[In, Out]) Out {
    return processor(input)
}

func main() {
    parser := Processor[string, int](func(input string) int { s, _ := strconv.Atoi(input); return s })
    doubler := Processor[int, int](func(input int) int { return input * 2 })
    outputer := Processor[int, string](func(input int) string { return fmt.Sprintf("%d", input) })

    out := Process(Process(Process("20", parser), doubler), outputer)
    fmt.Println(out)
}

游乐场: https://go.dev/play/p/iv-virkatyb

Is there a way to ignore the output type of the successor processor?

No.

In Go any is just a static type (alias of interface{}. It can never be a replacement for Java's unbounded wildcard ?. So *Processor[int, any] is just not the same type as *Processor[int, int] and you can't assign one to the other, as reported by your error message.

In order to construct an arbitrarily long chain you would need to parametrize the Process method itself, but this is not possible in Go 1.18. You must declare all type parameters on the type itself. Though, even if you do this, you will keep incurring in the same issue of not knowing the output type of the next processor.

Generally speaking, using a for loop can't work because the static types of the in/out values keep changing.

I believe the closest you can get without reflection is to implement some sort of composition operator — like . in haskell, via a top-level function. But you would have to manually nest calls.

A simplified example (the type Processor is redundant, but keeping it closer to your code):

package main

import (
    "fmt"
    "strconv"
)

type Processor[In, Out any] func(In) Out

func Process[In, Out any](input In, processor Processor[In, Out]) Out {
    return processor(input)
}

func main() {
    parser := Processor[string, int](func(input string) int { s, _ := strconv.Atoi(input); return s })
    doubler := Processor[int, int](func(input int) int { return input * 2 })
    outputer := Processor[int, string](func(input int) string { return fmt.Sprintf("%d", input) })

    out := Process(Process(Process("20", parser), doubler), outputer)
    fmt.Println(out)
}

Playground: https://go.dev/play/p/Iv-virKATyb

青朷 2025-01-31 13:49:57

您不能使用任何关键字来实例化通用类型的值。

nextProcessors     []*Processor[OutputType, any] // keyword any is not a valid type here

您实际上可以,但是第二个参数始终应为接口{}。但这不是您问题的答案的一部分。

要解决您的问题,您可以使用通用界面,而是

type IProcess[InputType any] interface {
    Process(input InputType)
}

type Processor[InputType any, OutputType any] struct {
    nextProcessors     []IProcess[OutputType]
    ProcessingFunction func(InputType) OutputType
}

func (b *Processor[InputType, OutputType]) AddOutputProcessor(p IProcess[OutputType]) {
    b.nextProcessors = append(b.nextProcessors, p)
}

https://go.dev/play/p/b1wlovsbb0i

You can't use any keyword to instantiate the value of generic type.

nextProcessors     []*Processor[OutputType, any] // keyword any is not a valid type here

You can actually, but the second parameter always should be interface{}. But it's not a part of answer to your question.

To solve your issue you can use generic interface instead

type IProcess[InputType any] interface {
    Process(input InputType)
}

type Processor[InputType any, OutputType any] struct {
    nextProcessors     []IProcess[OutputType]
    ProcessingFunction func(InputType) OutputType
}

func (b *Processor[InputType, OutputType]) AddOutputProcessor(p IProcess[OutputType]) {
    b.nextProcessors = append(b.nextProcessors, p)
}

https://go.dev/play/p/B1wlOvSbb0I

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文