调用接受接口A切片和结构体B切片(B实现A)的Go函数。

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

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{&quot;Nice Guy Eddie: C&#39;mon, throw in a buck!&quot;},
	Quote{&quot;Mr. Pink: Uh-uh, I don&#39;t tip.&quot;},
	Quote{&quot;Nice Guy Eddie: You don&#39;t tip?&quot;},
	Quote{&quot;Mr. Pink: Nah, I don&#39;t believe in it.&quot;},
	Quote{&quot;Nice Guy Eddie: You don&#39;t believe in tipping?&quot;},
}

// This doesn&#39;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 &amp;&amp; itsKind != reflect.Slice {
		return nil, fmt.Errorf(&quot;Expected items to be an Array or a Slice, got %s&quot;, itsKind)
	}
	itsLength := itsValue.Len()
	items := make([]Statement, itsLength)
	for i := 0; i &lt; itsLength; i++ {
		itsItem := itsValue.Index(i)
		if item, ok := itsItem.Interface().(Statement); ok {
			items[i] = item
		} else {
			return nil, fmt.Errorf(&quot;item #%d does not implement the Statement interface: %s&quot;, 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

对于你(长篇)问题的非常简短的答案是:不。

我认为你提出的ConvertToStatmentReplay接受空接口的解决方案并不是一个“好”的解决方案:我更喜欢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 &quot;fmt&quot;
    
    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 + &quot;: &quot; + 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{&quot;Nice Guy Eddie: C&#39;mon, throw in a buck!&quot;},
    		Quote{&quot;Mr. Pink: Uh-uh, I don&#39;t tip.&quot;},
    		Attributed{&quot;Nice Guy Eddie&quot;, &quot;You don&#39;t tip?&quot;},  // &lt;= another type
    		Quote{&quot;Mr. Pink: Nah, I don&#39;t believe in it.&quot;},
    		Quote{&quot;Nice Guy Eddie: You don&#39;t believe in tipping?&quot;},
    	}
    
    	myquotes := Quotes{
    		Quote{&quot;Nice Guy Eddie: C&#39;mon, throw in a buck!&quot;},
    		Quote{&quot;Mr. Pink: Uh-uh, I don&#39;t tip.&quot;},
    		Quote{&quot;Nice Guy Eddie: You don&#39;t tip?&quot;},
    		Quote{&quot;Mr. Pink: Nah, I don&#39;t believe in it.&quot;},
    		Quote{&quot;Nice Guy Eddie: You don&#39;t believe in tipping?&quot;},
    	}
    
    	Replay(conversation)
    	Replay(myquotes.toStatements())
    }

`Replay()` doesn&#39;t change or know anything about `Attributed{}`.
You do have to introduce types for the slices `Quotes` &amp; `Statements`.


</details>



huangapple
  • 本文由 发表于 2013年11月23日 22:40:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/20163660.html
匿名

发表评论

匿名网友

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

确定