Hello!
I would like to share my use for generics, as there have been a public
callout for feedback:
> Second, we know that many people have said that Go needs generics, but we don’t necessarily know exactly what that means. Does this draft design address the problem in a useful way? If there is a problem that makes you think “I could solve this if Go had generics,” can you solve the problem when using this tool?
My goal is to build a caller-friendly API for an engine, which
orchestrates processing of various commands. Each command has got a
specific input and output type. The engine, besides other things,
wraps command execution with various aspects (transactions,
performance logging, security checks, queueing,... and possibly
load-balancing to different nodes). Therefore, commands aren't
implemented as plain functions, but are implemented as handlers
identified by keys.
I had been playing with generics, and this the solution, which I came
up with (for simplistic, reduced, engine scratch) :
https://go2goplay.golang.org/p/1CRFNDCI6Lg
type Engine struct {
Commands map[string]func(input interface{}) interface{}
}
func (e *Engine) Invoke(key string, input interface{}) interface{} {
fmt.Printf("INVOKING COMMAND: %s\n", key)
return e.Commands[key](input)
}
type Command(type I, O) struct {
Key string
Handler func(input I) O
}
func (c Command(I, O)) GenericHandler(input interface{}) interface{} {
return c.Handler(input.(I))
}
func (c Command(I, O)) Invoke(engine *Engine, input I) O {
return engine.Invoke(c.Key, input).(O)
}
var c1 = Command(int, string){
Key: "C1",
Handler: func(n int) string {
return fmt.Sprintf("first command output %d", n)
},
}
var c2 = Command(int32, int64){
Key: "C2",
Handler: func(n int32) int64 {
return int64(n * 3)
},
}
type C3Input struct{ a, b int }
type C3Output struct{ x, y int }
var c3 = Command(C3Input, C3Output){
Key: "C3",
Handler: func(i C3Input) C3Output {
return C3Output{i.b, i.a}
},
}
func main() {
engine := &Engine{
Commands: map[string]func(input interface{}) interface{}{
c1.Key: c1.GenericHandler,
c2.Key: c2.GenericHandler,
c3.Key: c3.GenericHandler,
},
}
var (
// explicit types to demonstrate static type-checking
c1Result string = c1.Invoke(engine, 5)
c2Result int64 = c2.Invoke(engine, 7)
c3Result C3Output = c3.Invoke(engine, C3Input{11, 13})
)
fmt.Printf("Result #1: %#v\n", c1Result)
fmt.Printf("Result #2: %#v\n", c2Result)
fmt.Printf("Result #3: %#v\n", c3Result)
}
Above example doesn't thoroughly demonstrate the use of returned
variables, and static type checking. But, the goal is, that:
- input type is statically type-checked, when calling Invoke,
- output type is returned with correct static type, and can be used
for auto-completion and static type-checking.
Maybe, the engine's API can be designed in a better way. However, I
hope this feedback will be useful for designing Go's generics.
Thank you for the attention!