GoLang – Generics : Create variable in generic function

huangapple go评论86阅读模式
英文:

GoLang - Generics : Create variable in generic function

问题

我是你的中文翻译助手,以下是翻译好的内容:

我对Golang非常陌生(来自Java背景)。我试图在一个通用函数中为接口Writer创建一个变量(对象)类型,并尝试在同一个通用函数中调用接口函数,该函数创建了该变量(对象)。

由于遇到以下情况而无法继续。请帮助我。

谢谢。

package main

import "fmt"

type Writer interface {
	Write(message string)
	Print(message string)
	Test()
}

type FileLogger struct {
	ID int
}

func (f *FileLogger) Write(message string) {
	fmt.Println("Writing:", message)
	f.Test()
}

func (f *FileLogger) Print(message string) {
	fmt.Println("Logging:", message)
}

func (f *FileLogger) Test() {
	fmt.Println("Called Test:")
}

type SecondLogger struct {
	FileLogger
}

func (f *SecondLogger) Print(message string) {
	fmt.Println("SecondLogger:", message)
}

func (f *SecondLogger) Test() {
	fmt.Println("Updated Test")
}

type ThirdLogger struct {
	SecondLogger
}

func (f *ThirdLogger) Test() {
	fmt.Println("ThirdLogger Test")
}

func CreateLogger[T Writer]() *T {
	temp := new(T)
	temp.Test()
	temp.Write("Hello, World!")
	return temp
}

func main() {
	var writer Writer

	writer = CreateLogger[FileLogger]() //Error
	writer.Print("This is a log message.")

	writer = CreateLogger[SecondLogger]() //Error
	writer.Print("2nd log message.")

	writer = CreateLogger[ThirdLogger]() //Error
	writer.Print("3rd log message.")
	writer.Test()
}

更新:
期望结果:
使用相同的createLogger函数,我希望能够在创建对象的同时调用Test()函数,以创建所有3种类型的结构体对象。

英文:

I am Very new to Golang (came from Java background). I am trying to create a variable(object) type for interface Writer in a generic function and try to call interface function in same generic function which create the variable(object).

Could Not able to proceed since struck with below case. Please help me.

Thanks.

package main
import "fmt"
type Writer interface {
Write(message string)
Print(message string)
Test()
}
type FileLogger struct {
ID int
}
func (f *FileLogger) Write(message string) {
fmt.Println("Writing:", message)
f.Test()
}
func (f *FileLogger) Print(message string) {
fmt.Println("Logging:", message)
}
func (f *FileLogger) Test() {
fmt.Println("Called Test:")
}
type SecondLogger struct {
FileLogger
}
func (f *SecondLogger) Print(message string) {
fmt.Println("SecondLogger:", message)
}
func (f *SecondLogger) Test() {
fmt.Println("Updated Test")
}
type ThirdLogger struct {
SecondLogger
}
func (f *ThirdLogger) Test() {
fmt.Println("ThirdLogger Test")
}
func CreateLogger[T Writer]() *T {
temp := new(T)
temp.Test()
temp.Write("Hello, World!")
return temp
}
func main() {
var writer Writer
writer = CreateLogger[FileLogger]()//Error
writer.Print("This is a log message.")
writer = CreateLogger[SecondLogger]()//Error
writer.Print("2nd log message.")
writer = CreateLogger[ThirdLogger]()//Error
writer.Print("3rd log message.")
writer.Test()
}

Update :
Expectation:

Using same createLogger Function I could able to create object for all 3 type of struct while creating itself should be able call to Test() function

答案1

得分: 1

你不需要使用golang的最新泛型功能来实现这个。你可以只使用接口的概念来实现。以下是代码。

package main

import "fmt"

type Writer interface {
	Write(message string)
	Print(message string)
	Test()
}

type FileLogger struct {
	ID int
}

func (f *FileLogger) Write(message string) {
	fmt.Println("Writing:", message)
	f.Test()
}

func (f *FileLogger) Print(message string) {
	fmt.Println("Logging:", message)
}

func (f *FileLogger) Test() {
	fmt.Println("Called Test:")
}

type SecondLogger struct {
	FileLogger
}

func (f *SecondLogger) Print(message string) {
	fmt.Println("SecondLogger:", message)
}

func (f *SecondLogger) Test() {
	fmt.Println("Updated Test")
}

type ThirdLogger struct {
	SecondLogger
}

func (f *ThirdLogger) Test() {
	fmt.Println("ThirdLogger Test")
}

func CreateLogger(kind interface{}) Writer {
	switch kind.(type) {
	case FileLogger:
		return &FileLogger{}
	case SecondLogger:
		return &SecondLogger{}
	case ThirdLogger:
		return &ThirdLogger{}
	default:
		return nil
	}
}

func main() {
	var writer Writer

	writer = CreateLogger(FileLogger{})
	writer.Print("This is a log message.")

	writer = CreateLogger(SecondLogger{})
	writer.Print("2nd log message.")

	writer = CreateLogger(ThirdLogger{})
	writer.Print("3rd log message.")
	writer.(*ThirdLogger).Test()
}

你可以在Go Playground上运行这段代码。

英文:

You don't need golangs latest generics feature to do this. You can achieve this just using interface concepts. Here is the code. Go Playground

package main
import "fmt"
type Writer interface {
Write(message string)
Print(message string)
Test()
}
type FileLogger struct {
ID int
}
func (f *FileLogger) Write(message string) {
fmt.Println("Writing:", message)
f.Test()
}
func (f *FileLogger) Print(message string) {
fmt.Println("Logging:", message)
}
func (f *FileLogger) Test() {
fmt.Println("Called Test:")
}
type SecondLogger struct {
FileLogger
}
func (f *SecondLogger) Print(message string) {
fmt.Println("SecondLogger:", message)
}
func (f *SecondLogger) Test() {
fmt.Println("Updated Test")
}
type ThirdLogger struct {
SecondLogger
}
func (f *ThirdLogger) Test() {
fmt.Println("ThirdLogger Test")
}
func CreateLogger(kind interface{}) Writer {
switch kind.(type) {
case FileLogger:
return &FileLogger{}
case SecondLogger:
return &SecondLogger{}
// here v has type S
case ThirdLogger:
return &ThirdLogger{}
default:
return nil
}
}
func main() {
var writer Writer
writer = CreateLogger(FileLogger{})
writer.Print("This is a log message.")
writer = CreateLogger(SecondLogger{})
writer.Print("2nd log message.")
writer = CreateLogger(ThirdLogger{})
writer.Print("3rd log message.")
writer.(*ThirdLogger).Test()
}

答案2

得分: 1

GoPlayground上运行你的代码会出现以下错误:

./prog.go:52:7: temp.Test未定义(*T是指向类型参数的指针,而不是类型参数)
./prog.go:53:7: temp.Write未定义(*T是指向类型参数的指针,而不是类型参数)
./prog.go:60:24: FileLogger不满足Writer接口(Print方法具有指针接收器)
./prog.go:63:24: SecondLogger不满足Writer接口(Print方法具有指针接收器)
./prog.go:66:24: ThirdLogger不满足Writer接口(Print方法具有指针接收器)

让我们来处理这些错误:

这些错误的一部分是因为在调用接口函数时使用了指针:

./prog.go:60:24: FileLogger不满足Writer接口(Print方法具有指针接收器)
./prog.go:63:24: SecondLogger不满足Writer接口(Print方法具有指针接收器)
./prog.go:66:24: ThirdLogger不满足Writer接口(Print方法具有指针接收器)

因此,需要进行以下更改:

  • func (f *FileLogger) Write更改为func (f FileLogger) Write
  • func (f *FileLogger) Print更改为func (f FileLogger) Print
  • func (f *FileLogger) Test更改为func (f FileLogger) Test
  • func (f *SecondLogger) Print更改为func (f SecondLogger) Print
  • func (f *SecondLogger) Test更改为func (f SecondLogger) Test
  • func (f *ThirdLogger) Test更改为func (f ThirdLogger) Test

通过以上更新,这三行错误将消失。

但有趣的是(这是由于在Golang语言中使用指针导致的),通过避免使用通用的创建函数(直接创建它们),不需要进行上述更新,并且使用指针不会导致任何错误。

(然而,请注意这些函数不会对其调用者的结构进行任何更改,因此不需要使用指针)

让我们继续处理以下错误:

./prog.go:52:7: temp.Test未定义(*T是指向类型参数的指针,而不是类型参数)
./prog.go:53:7: temp.Write未定义(*T是指向类型参数的指针,而不是类型参数)

这些错误是因为声明了func CreateLogger[T Writer]() *T
GoLang的接口方法与Java略有不同。
在Java中,当一个类实现一个接口时,它被认为是该接口的一种类型。这个概念基于面向对象编程(OOP)和继承的原则。
但是通过在func CreateLogger[T Writer]内部使用temp := new(T),你创建的是一个Writer,而不是FileLogger、SecondLogger或ThirdLogger(在GoLang中,实现接口方法的结构体不像Java那样被认为是接口的一种类型)。正如错误中所提到的,使用new(T)会创建指向类型参数的指针,而不是类型参数本身。

因此,使用@shahriar-ahmed的答案可以帮助你避免使用通用特性,但如果你坚持实现通用的创建函数,一种方法是使用反射:

func CreateLogger[T Writer]() T {
// 创建类型T的默认模型
var temp T
// 检查类型T是否实现了Writer接口
if reflect.TypeOf(temp).Implements(reflect.TypeOf((*Writer)(nil)).Elem()) {
// 如果存在,调用Test方法
if testFunc, ok := reflect.TypeOf(temp).MethodByName("Test"); ok {
reflect.ValueOf(temp).Method(testFunc.Index).Call(nil)
}
// 如果存在,调用Write方法
if writeFunc, ok := reflect.TypeOf(temp).MethodByName("Write"); ok {
reflect.ValueOf(temp).Method(writeFunc.Index).Call([]reflect.Value{reflect.ValueOf("Hello, World!")})
}
}
return temp
}

你可以在这个GoPlayground链接中查看和运行最终更新的代码。

英文:

Running your code on GoPlayground makes these errors:

./prog.go:52:7: temp.Test undefined (type *T is pointer to type parameter, not type parameter)
./prog.go:53:7: temp.Write undefined (type *T is pointer to type parameter, not type parameter)
./prog.go:60:24: FileLogger does not satisfy Writer (method Print has pointer receiver)
./prog.go:63:24: SecondLogger does not satisfy Writer (method Print has pointer receiver)
./prog.go:66:24: ThirdLogger does not satisfy Writer (method Print has pointer receiver)

Lets handle them:

These part of errors occurs because of using pointers for calling interface functions:

./prog.go:60:24: FileLogger does not satisfy Writer (method Print has pointer receiver)
./prog.go:63:24: SecondLogger does not satisfy Writer (method Print has pointer receiver)
./prog.go:66:24: ThirdLogger does not satisfy Writer (method Print has pointer receiver)

So, below changing is needed:

  • func (f *FileLogger) Write to func (f FileLogger) Write
  • func (f *FileLogger) Print to func (f FileLogger) Print
  • func (f *FileLogger) Test to func (f FileLogger) Test
  • func (f *SecondLogger) Print to func (f SecondLogger) Print
  • func (f *SecondLogger) Test to func (f SecondLogger) Test
  • func (f *ThirdLogger) Test to func (f ThirdLogger) Test

With the above update, those three lines of errors will disappear.

But the interesting thing (which is caused by the use of pointers in Golang language) is by prevent using generic creator function (and create them directly), there is no need to do the above updates and using Pointers doesn't cause any errors.

(However, consider that those functions are not going to make any changes to their caller structs, so there is no need to use pointers)

Lets go on these:

./prog.go:52:7: temp.Test undefined (type *T is pointer to type parameter, not type parameter)
./prog.go:53:7: temp.Write undefined (type *T is pointer to type parameter, not type parameter)

These errors occurs because of declaring func CreateLogger[T Writer]() *T.
GoLang has slightly different Interface approach from Java.
In Java, when a class implements an interface, it is considered to be a kind of that interface. This concept is based on the principles of object-oriented programming (OOP) and inheritance.
But by temp := new(T) inside func CreateLogger[T Writer], you are creating a Writer, not FileLogger or SecondLogger or ThirdLogger (in GoLang, the structs that implements an interface methods, are not considered as kind of the interface like Java). Also as mentioned in errors, using new(T), creates pointer to type parameter, not type parameter.

So, using @shahriar-ahmeds answer helps you by prevent using generic features, but if you insist implementing generics creator function, one way is using reflect:

func CreateLogger[T Writer]() T {
// Create a default model of type T
var temp T
// Check if the type T implements the Writer interface
if reflect.TypeOf(temp).Implements(reflect.TypeOf((*Writer)(nil)).Elem()) {
// Call the Test method if it exists
if testFunc, ok := reflect.TypeOf(temp).MethodByName("Test"); ok {
reflect.ValueOf(temp).Method(testFunc.Index).Call(nil)
}
// Call the Write method if it exists
if writeFunc, ok := reflect.TypeOf(temp).MethodByName("Write"); ok {
reflect.ValueOf(temp).Method(writeFunc.Index).Call([]reflect.Value{reflect.ValueOf("Hello, World!")})
}
}
return temp
}

You can see and run the final updated code in this GoPlayground link.

huangapple
  • 本文由 发表于 2023年7月5日 22:10:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76621206.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定