英文:
Call Go function that accepts a slice of interface A with a slice of struct B (B implements A)
问题
我有以下类型:
type Statement interface {
Say() string
}
type Quote struct {
quote string
}
func (p Quote) Say() string {
return p.quote
}
func Replay(conversation []Statement) {
for _, statement := range conversation {
fmt.Println(statement.Say())
}
}
我认为我对于为什么一个接受类型为[]Statement
参数的函数不能用[]Quote
调用有相当好的理解;尽管Quote
实现了Statement
,但[]Quote
并没有实现[]Statement
。[]Statement
甚至不是一个接口。它的类型是Statement
的切片。虽然Go会隐式地将类型转换为接口类型,但它不会隐式地将类型为A
的切片转换为接口类型为B
的切片。
我们可以显式地将引用转换为语句:
conversation := []Quote{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Quote{"Nice Guy Eddie: You don't tip?"},
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
// 这样是不行的
// Replay(conversation)
// 从引用创建语句
statements := make([]Statement, len(conversation))
for i, quote := range conversation {
statements[i] = quote
}
Replay(statements)
现在假设Replay是一个库的一部分,该库希望在使用Replay时尽可能简单。只要这些对象实现了Statement接口,它允许您使用任何对象的切片调用Replay。为此,它具有以下转换方法:
func ConvertToStatements(its interface{}) ([]Statement, error) {
itsValue := reflect.ValueOf(its)
itsKind := itsValue.Kind()
if itsKind != reflect.Array && itsKind != reflect.Slice {
return nil, fmt.Errorf("Expected items to be an Array or a Slice, got %s", itsKind)
}
itsLength := itsValue.Len()
items := make([]Statement, itsLength)
for i := 0; i < itsLength; i++ {
itsItem := itsValue.Index(i)
if item, ok := itsItem.Interface().(Statement); ok {
items[i] = item
} else {
return nil, fmt.Errorf("item #%d does not implement the Statement interface: %s", i, itsItem)
}
}
return items, nil
}
Replay的代码如下:
func Replay(its interface{}) {
conversation := ConvertToStatements(its)
for _, statement := range conversation {
fmt.Println(statement.Say())
}
}
现在我们可以直接使用引用调用Replay:
Replay(conversation)
最后,我的问题是:是否有一种更简单的方法允许Replay接受任何类型A的切片,只要A实现了Statement接口?
英文:
I have the following types:
type Statement interface {
Say() string
}
type Quote struct {
quote string
}
func (p Quote) Say() string {
return p.quote
}
func Replay(conversation []Statement) {
for _, statement := range conversation {
fmt.Println(statement.Say())
}
}
I think I have a fairly good grasp of why a function that accepts a parameter of type []Statement
, cannot be called with []Quote
; even though Quote
implements Statement
, []Quote
does not implement []Statement
. []Statement
is not even an interface. It has the type slice of Statement
. While Go implicitly converts from a type to an interface type, it does no implicit conversion from a slice of type A
to a slice of interface B
.
We can convert the quotes to statements explicitly:
conversation := []Quote{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Quote{"Nice Guy Eddie: You don't tip?"},
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
// This doesn't work
// Replay(conversation)
// Create statements from quotes
statements := make([]Statement, len(conversation))
for i, quote := range conversation {
statements[i] = quote
}
Replay(statements)
Now say that Replay is part of a library that wants to go out of its way in how easy it's to use Replay. It allows you to call Replay with any slice of objects as long as those objects implement the Statement interface. To do so it has the following conversion method:
func ConvertToStatements(its interface{}) ([]Statement, error) {
itsValue := reflect.ValueOf(its)
itsKind := itsValue.Kind()
if itsKind != reflect.Array && itsKind != reflect.Slice {
return nil, fmt.Errorf("Expected items to be an Array or a Slice, got %s", itsKind)
}
itsLength := itsValue.Len()
items := make([]Statement, itsLength)
for i := 0; i < itsLength; i++ {
itsItem := itsValue.Index(i)
if item, ok := itsItem.Interface().(Statement); ok {
items[i] = item
} else {
return nil, fmt.Errorf("item #%d does not implement the Statement interface: %s", i, itsItem)
}
}
return items, nil
}
Replay looks like this:
func Replay(its interface{}) {
conversation := ConvertToStatements(its)
for _, statement := range conversation {
fmt.Println(statement.Say())
}
}
We can now call Replay with quotes directly:
Replay(conversation)
Finally, my question: Is there a simpler way to allow Replay to accept a slice of any type A, as long as A implements the Statement interface?
答案1
得分: 6
[]Quote
切片的内存布局与[]Statement
切片不同,因此无法进行转换。
[]Quote
切片的底层数组由连续的Quote
结构体组成,而[]Statement
切片的底层数组由接口变量组成。除了保存Quote
结构体(或其他实现该接口的类型),接口变量还存储了指向所包含值的类型信息的指针。这是为了确定如何调度Say
方法调用所必需的。
不同的数据布局意味着无法互换这两种切片类型,即使通过不安全的类型转换也不行:如果你有一种类型并且需要另一种类型,你需要手动在它们之间进行转换。
英文:
The in-memory layout of a []Quote
slice is different to a []Statement
slice, so this is not possible.
The backing array of the []Quote
slice will consist of the sequential Quote
structs, while the []Statement
slice's backing array consists of interface variables. As well as holding the Quote
struct (or whatever other type implements the interface), the interface variable also stores a pointer to type information for the contained value. This is needed to determine how to dispatch the Say
method call.
The different data layout means that you can't interchange the two slice types, not even through unsafe casts: if you have one type and need the other you'll need to manually convert between them.
答案2
得分: 3
对于你(长篇)问题的非常简短的答案是:不。
我认为你提出的ConvertToStatment
和Replay
接受空接口的解决方案并不是一个“好”的解决方案:我更喜欢func Replay([]Statement)
,调用者必须提供一个Statement
的切片。这样更清晰,调用者可以将他们的内容转换为[]Statement
,或者直接构造一个[]Statement
。
英文:
The very short answer to your (long) question is: No.
I don't think that your solution of ConvertToStatment and Replay taking an empty interface is a "nice" solution: I'd prefer func Replay([]Statement)
and callers must provide a slice of Statments. This is much clearer and callers can either convert their stuff to []Statement or directly construct a []Statement.
答案3
得分: 1
以下代码有两种不同的结构类型,它们都实现了Say()
函数。你可以创建一个包含这两种类型的数组,并调用Replay()
函数,让它按照你的要求执行:
package main
import "fmt"
type Statement interface {
Say() string
}
type Statements []Statement
type Quote struct {
quote string
}
type Quotes []Quote
func (p Quote) Say() string {
return p.quote
}
type Attributed struct {
who string
quote string
}
func (p Attributed) Say() string {
return p.who + ": " + p.quote
}
func Replay(conversation []Statement) {
for _, s := range conversation {
fmt.Println(s.Say())
}
}
func (q Quotes) toStatements() Statements {
conv := make(Statements, len(q))
for i, v := range q {
conv[i] = Statement(v)
}
return conv
}
func main() {
conversation := Statements{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Attributed{"Nice Guy Eddie", "You don't tip?"}, // <= 另一种类型
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
myquotes := Quotes{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Quote{"Nice Guy Eddie: You don't tip?"},
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
Replay(conversation)
Replay(myquotes.toStatements())
}
`Replay()`函数对`Attributed{}`没有任何改变或了解。
你需要为切片`Quotes`和`Statements`引入类型。
<details>
<summary>英文:</summary>
The following code has two different structure types that both implement the `Say()` function. You can create an array containing both types and call `Replay()`and have it do what you want:
package main
import "fmt"
type Statement interface {
Say() string
}
type Statements []Statement
type Quote struct {
quote string
}
type Quotes []Quote
func (p Quote) Say() string {
return p.quote
}
type Attributed struct {
who string
quote string
}
func (p Attributed) Say() string {
return p.who + ": " + p.quote
}
func Replay(conversation []Statement) {
for _, s := range conversation {
fmt.Println(s.Say())
}
}
func (q Quotes) toStatements() Statements {
conv := make(Statements, len(q))
for i, v := range q {
conv[i] = Statement(v)
}
return conv
}
func main() {
conversation := Statements{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Attributed{"Nice Guy Eddie", "You don't tip?"}, // <= another type
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
myquotes := Quotes{
Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
Quote{"Mr. Pink: Uh-uh, I don't tip."},
Quote{"Nice Guy Eddie: You don't tip?"},
Quote{"Mr. Pink: Nah, I don't believe in it."},
Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}
Replay(conversation)
Replay(myquotes.toStatements())
}
`Replay()` doesn't change or know anything about `Attributed{}`.
You do have to introduce types for the slices `Quotes` & `Statements`.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论