Go中的通用可变参数?

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

Generic variadic argument in Go?

问题

我知道Go语言不支持模板或重载函数,但我想知道是否有办法为可变参数函数实现某种通用编程?

我有很多类似这样的函数:

func (this Document) GetString(name string, defaults ...string) string {
v, ok := this.GetValueFromDb(name)
if !ok {
if len(defaults) >= 1 {
return defaults[0]
} else {
return ""
}
}
return v.asString
}

func (this Document) GetInt(name string, defaults ...int) int {
v, ok := this.GetValueFromDb(name)
if !ok {
if len(defaults) >= 1 {
return defaults[0]
} else {
return 0
}
}
return v.asInt
}

// 等等,还有很多不同类型的函数

有没有办法在不重复代码的情况下实现这个功能?

英文:

I know that Go doesn't support templates or overloaded functions, but I'm wondering if there's any way to do some kind of generic programming for variadic functions anyway?

I have many functions such as these:

func (this Document) GetString(name string, defaults ...string) string {
	v, ok := this.GetValueFromDb(name)
	if !ok {
		if len(defaults) >= 1 {
			return defaults[0]
		} else {
			return ""
		}
	}
	return v.asString
}

func (this Document) GetInt(name string, defaults ...int) int {
	v, ok := this.GetValueFromDb(name)
	if !ok {
		if len(defaults) >= 1 {
			return defaults[0]
		} else {
			return 0
		}
	}
	return v.asInt
}

// etc. for many different types

Is there any way to do this without having so much redundant code?

答案1

得分: 12

你可以通过使用interface{}类型来实现大部分功能,就像这样:

func (this Document) Get(name string, defaults ...interface{}) interface{} {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(defaults) >= 1 {
            return defaults[0]
        } else {
            return 0
        }
    }
    return v
}

GetValueFromDb函数也应该被调整为返回interface{}类型的值,而不是像现在这样的包装器。

然后在客户端代码中,你可以这样做:

value := document.Get("index", 1).(int)  // 当值不是int类型时会引发panic

或者

value, ok := document.Get("index", 1).(int)  // 如果值不是int类型,ok为false

不过这样会增加一些运行时开销。最好还是坚持使用单独的函数,并尝试重新组织代码结构。

英文:

The most of what you can achieve is usage of interface{} type, something like this:

func (this Document) Get(name string, defaults ...interface{}) interface{} {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(defaults) >= 1 {
            return defaults[0]
        } else {
            return 0
        }
    }
    return v
}

GetValueFromDb function should also be tweaked to return interface{} value and not some wrapper like now.

Then in the client code you can do the following:

value := document.Get("index", 1).(int)  // Panics when the value is not int

or

value, ok := document.Get("index", 1).(int)  // ok is false if the value is not int

This will yield some runtime overhead though. I'd better stick with separate functions and try to restructure the code somehow.

答案2

得分: 2

这是一个工作示例,展示了如何改变你的代码。

package main

import (
	"fmt"
)

type Document struct{
	getSucceeds bool
}

func (d *Document) GetValueFromDb(name string) (interface{}, bool) {
	return 1, d.getSucceeds
}

func (this Document) Get(name string, def ...int) interface{} {
	v, ok := this.GetValueFromDb(name)
	if !ok {
		if len(def) >= 1 {
			return def[0]
		} else {
			return 0
		}
	}
	return v
}

func main() {
	d1 := Document{true}
	d2 := Document{false}
	
	var int1, int2 int
	
	int1 = d1.Get("foo", 2).(int)
	int2 = d2.Get("foo", 2).(int)
	
	fmt.Println(int1, int2)
}

由于你知道给定名称的类型,你可以以通用的方式编写你的Get方法,返回interface{},然后在调用处断言类型。请参阅关于type assertions的规范。

在Go中,有不同的方法来模拟泛型的某些方面。在邮件列表上进行了很多讨论。通常,有一种方法可以重构代码,使其对泛型的依赖性较小。

英文:

Here's a working example of how you could change your code.

package main

import (
"fmt"
)

type Document struct{
	getSucceeds bool
}

func (d *Document) GetValueFromDb(name string) (interface{}, bool) {
	return 1, d.getSucceeds
}

func (this Document) Get(name string, def ...int) interface{} {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(def) >= 1 {
            return def[0]
        } else {
            return 0
        }
    }
    return v
}

func main() {
	d1 := Document{true}
	d2 := Document{false}
	
	var int1, int2 int
	
	int1 = d1.Get("foo", 2).(int)
	int2 = d2.Get("foo", 2).(int)
	
	fmt.Println(int1, int2)
}

Since you know what type you expect for the given name, you can write your Get method in a generic way, returning interface{}, and then assert the type at the call site. See the spec about type assertions.

There are different ways to emulate some aspects of generics in Go. There were lots of discussions on the mailing list. Often, there's a way to restructure code so it's less dependent on generics.

答案3

得分: 0

在客户端代码中,您可以这样做:

res := GetValue("name", 1, 2, 3)
// 或者
// res := GetValue("name", "one", "two", "three")

if value, ok := res.(int); ok {
    // 处理整数返回值
} else if value, ok := res.(string); ok {
    // 处理字符串返回值
}

// 或者
// res.(type) 表达式只能在 switch 语句中使用
// 并且 'res' 变量的类型必须是接口类型
switch value := res.(type) {
case int:
    // 处理整数返回值
case string:
    // 处理字符串返回值
}
英文:

In the client code you can do like this :

res := GetValue("name", 1, 2, 3)
// or
// res := GetValue("name", "one", "two", "three")

if value, ok := res.(int); ok {
    // process int return value
} else if value, ok := res.(string); ok {
    // process string return value
}

// or
// res.(type) expression only work in switch statement
// and 'res' variable's type have to be interface type
switch value := res.(type) {
case int:
    // process int return value
case string:
    // process string return value
}

huangapple
  • 本文由 发表于 2013年2月27日 13:24:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/15104795.html
匿名

发表评论

匿名网友

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

确定