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?
Like a struct an interface is created using the type keyword, followed by a name and the keyword interface . But instead of defining fields, we define a “method set”. A method set is a list of methods that a type must have in order to “implement” the interface.
Go slice is reference type A slice is a reference type in Go. This means that when we assign a reference to a new variable or pass a slice to a function, the reference to the slice is copied. In the code example, we define a slice and assign the slice to a new variable.
In Go language slice is more powerful, flexible, convenient than an array, and is a lightweight data structure. Slice is a variable-length sequence which stores elements of a similar type, you are not allowed to store different type of elements in the same slice.
To add an element to a slice , you can use Golang's built-in append method. append adds elements from the end of the slice. The first parameter to the append method is a slice of type T . Any additional parameters are taken as the values to add to the given slice .
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
.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With