Say I have functions:
func ToModelList(cats *[]*Cat) *[]*CatModel {
list := *cats
newModelList := []*CatModel{}
for i := range list {
obj := obj[i]
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
func ToModelList(dogs *[]*Dog) *[]*DogModel {
list := *dogs
newModelList := []*DogModel{}
for i := range list {
obj := obj[i]
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
Is there a way to combine those two so I can do something like
func ToModelList(objs *[]*interface{}) *[]*interface{} {
list := *objs
// figure out what type struct type objs/list are
newModelList := []*interface{}
// type cast newModelList to the correct array struct type
for i := range list {
obj := obj[i]
// type cast obj based on objs's type
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
First, slices are already a reference, unless you need to change the slice itself, you do not need to pass it as a pointer.
Second, an interface{} can be regardless an object or a pointer to an object. You do not need to have *interface{}.
I am not sure what you are trying to achieve but you could do something like this:
package main
// Interface for Cat, Dog
type Object interface {
ToModel() Model
}
// Interface for CatModel, DogModel
type Model interface {
Name() string
}
type Cat struct {
name string
}
func (c *Cat) ToModel() Model {
return &CatModel{
cat: c,
}
}
type CatModel struct {
cat *Cat
}
func (c *CatModel) Name() string {
return c.cat.name
}
type Dog struct {
name string
}
func (d *Dog) ToModel() Model {
return &DogModel{
dog: d,
}
}
type DogModel struct {
dog *Dog
}
func (d *DogModel) Name() string {
return d.dog.name
}
func ToModelList(objs []Object) []Model {
newModelList := []Model{}
for _, obj := range objs {
newModelList = append(newModelList, obj.ToModel())
}
return newModelList
}
func main() {
cats := []Object{
&Cat{name: "felix"},
&Cat{name: "leo"},
&Dog{name: "octave"},
}
modelList := ToModelList(cats)
for _, model := range modelList {
println(model.Name())
}
}
You define interfaces for your Cat, Dogs etc and for your Model. Then you implement them as you want and it is pretty straight forward to do ToModelList().
you can make *CatModel and *DogModel both implement type PetModel {} interface, and just return []Pet in function signature.
func (cats []*Cat) []PetModel {
...
return []*CatModel {...}
}
func (dogs []*Dog) []PetModel {
...
return []*DogModel {...}
}
BTW: return a pointer of a slice in golang is useless.
If you strip away redundant assignments, and unnecessary pointers-to-slices, you'll find you have little code left, and duplicating it for each of your model types doesn't look so bad.
func CatsToCatModels(cats []*Cat) []*CatModel {
var result []*CatModel
for _, cat := range cats {
result = append(result, cat.ToModel())
}
return result
}
Unless this code is used in a lot of places I'd also consider just inlining it, since it's trivial code and only 4 lines when inlined.
Yes, you can replace all the types with interface{} and make the code generic, but I don't think it's a good tradeoff here.
Related
Is there a way to create a generic function that can adjust its operation when passed a map or a slice type vs a basic type?
Goal
Create a slice reading function generator with a flexible return type:
func ValueReader[T <probably something fancy>](i int) func ([]ProtoConvertable) T {
return func (row []ProtoConvertable) T {
return ...
}
}
row := []ProtoConvertable{
&Data[int]{Value: 333},
&ListData{Values: []ProtoConvertable{
&Data[string]{Value: "hello"},
&Data[string]{Value: "world"},
}},
&MapData{Values: map[ProtoConvertable]ProtoConvertable{
&Data[int]{Value: 22}: &Data[string]{Value: "world"},
&Data[int]{Value: 11}: &Data[string]{Value: "hello"},
}},
}
dataReader := ValueReader[int](0) // A function that converts the first element to an int
listDataReader := ValueReader[[]string](1) // A function that converts the second element to a slice
mapDataReader := ValueReader[map[int]string](2) // A function that converts the third element to a map
data := dataReader(row) // 333
listData := listDataReader(row) // []string{"hello", "world"}
mapData := mapDataReader(row) // map[int]string{11: "hello", 22: "world"}
Types
type ValueType interface {
int | string
}
type ProtoConvertable interface {
ToProto() *pb.GenericMessage
}
type Data[T ValueType] struct {
Value T
}
func (d *Data) ToProto() *pb.GenericMessage{
...
}
type ListData struct {
Values []ProtoConvertable
}
func (d *ListData) ToProto() *pb.GenericMessage {
...
}
type MapData struct {
Values map[ProtoConvertable]ProtoConvertable
}
func (d *MapData) ToProto() *pb.GenericMessage {
...
}
Current Solution
func ValueReader[T ValueType](i int) func([]ProtoConvertable) T {
return func(row []ProtoConvertable) T {
return row[i].(*Data[T]).Value
}
}
func ListValueReader[T ValueType](i int) func([]ProtoConvertable) []T {
return func(row []ProtoConvertable) []T {
vs := row[i].(*ListData).Values
res := make([]T, len(vs))
for i, v := range vs {
res[i] = v.(*Data[T]).Value
}
return res
}
}
func MapValueReader[K ValueType, V ValueType](i int) func([]ProtoConvertable) map[K]V {
return func(row []ProtoConvertable) map[K]V {
vs := row[i].(*MapData).Values
res := make(map[K]V, len(vs))
for k, v := range vs {
res[k.(*Data[K]).Value] = v.(*Data[V]).Value
}
return res
}
}
dataReader := ValueReader[int](0)
listDataReader := ListValueReader[string](1)
mapDataReader := MapValueReader[int, string](2)
Note: all of this code is an untested simplification of a more complicated library. It might need some tweaking to get to actually work.
The <probably something fancy> doesn't exist.
The main issue is that you want to model a type parameter that matches a base value and two composite types, one of which is a map type where you want to capture both K and V.
Even if it existed, the body of ValueReader would be a type-switch on T to return each specialized reader function, so your existing solution that involves a small amount of code duplication seems just a better strategy overall.
My advice is to use generics when the operations on the different concrete types of T are really identical. You can read more at: https://go.dev/blog/when-generics
I'm trying to write a common module using Go interface. It results in an error:
panic: interface conversion: interface {} is main.AmazonOrder, not main.OrderMapper
Code:
package main
type AmazonOrder struct {
OrderId string
ASIN string
}
func (o AmazonOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type EbayOrder struct {
OrderId string
SKU string
}
func (o EbayOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type Order struct {
ID string
ChannelOrderId string
}
type OrderMapper struct {
Generalise func() *Order
}
var orderFactory = map[string]func() interface{} {
"amazon": func() interface{} {
return AmazonOrder{}
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId)
}
In plain sight logic, this should work fine. But, definitely I'm misunderstanding the type conversion in Go. TIA for clarifying what it is.
Playground: https://play.golang.org/p/tHCzKGzEloL
you have to use interface, not a struct with function field, i left comments around the ganges.
// OrderMapper is probably ment to be an interface. You can convert interface
// to struct only if its the original one. Though you need to convert it into iterface in
// your case because type of return value is variable. As all orders implement
// 'Generalise' and you are planing to convert empty interface to OderMapper why not just
// return order mapper right away? also best practice to name your interface, with just
// one method, is methodName + 'er' so Generalizer.
type OrderMapper interface {
Generalise() *Order
}
/* alternative
var orderFactory = map[string]func() OrderMapper{
"amazon": func() OrderMapper {
return AmazonOrder{OrderId: "amazonOrderID"}
},
"ebay": func() OrderMapper {
return EbayOrder{}
},
"vanilla": func() OrderMapper {
return Order{}
},
}
*/
var orderFactory = map[string]func() interface{}{
"amazon": func() interface{} {
return AmazonOrder{OrderId: "amazonOrderID"} // i added a value to verify correctness
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
// alternative: theOrder := anOrder.Generalise()
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId == "amazonOrderID") // true
}
I have multiple structs like bellow
type Person struct {
first string
last string
age int
}
type Product struct {
ProductID int
Name string
Description string
Price float64
}
I want a function that will take a slice of any type as the first argument and a function as a second argument that it will use to call with each element of the slice to construct a string slice and will return that string slice. Something like map() in Typescript/scala or select() in C#.
Since Go doesn't have generics, "any type" can only mean interface{}. You can have something like this:
func ToStringSlice(arr []interface{}, convert func(interface{}) string) []string {
ret := []string{}
for _, elem := range arr {
ret = append(ret, convert(elem))
}
return ret
}
Then you can basically inject any conversion function you want. e.g.
fmt.Println(ToStringSlice([]interface{}{1, 2, "foo", "bar"},
func(x interface{}) string {
return fmt.Sprint(x)
}))
And since string conversions can go bad, I'd also add error checking:
// Define the function with an error return.
type Stringifier func(interface{}) (string, error)
func ToStringSlice(arr []interface{}, convert Stringifier) ([]string, error) {
ret := []string{}
for _, elem := range arr {
if s, e := convert(elem); e != nil {
return nil, e
} else {
ret = append(ret, s)
}
}
return ret, nil
}
one solution involves fmt.Stringer
package main
import (
"fmt"
)
func main() {
items := []fmt.Stringer{Product{ProductID: 1}, Person{first: "first"}}
var res []string
for _, i := range items {
res = append(res, i.String())
}
fmt.Println(res)
}
type Person struct {
first string
last string
age int
}
func (p Person) String() string { return p.first }
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func (p Product) String() string { return fmt.Sprint(p.ProductID) }
That's a typical use case for an interface. Luckily enough the interface you need already exists as fmt.Stringer (https://golang.org/pkg/fmt/#Stringer):
Have a look at https://play.golang.org/p/d1sNPLKhNCU...
First your structs are required to implement the Stringer interface. This is done by simply adding a menthode String(), for example:
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func (p Product) String() string {
return fmt.Sprintf("%d %s", p.ProductID, p.Name)
}
Now your Product is also a Stringer and can be Therefore be passed in any function that accepts Stringers:
func AsStringSlice(in []fmt.Stringer) []string {
out := []string{}
for _, i := range in {
out = append(out, i.String())
}
return out
}
Since "interface{} says nothing" (https://go-proverbs.github.io/) I would recommend to use interface{} as rarely as possible. This is achieved in this approach. Also you can avoid reflection that way.
On the other hand you have to be able to manage the structs, eg. implement the String function. If thats not possible because the Types come from a dependency consider wrapping them:
type MyTypeWithStringer package.ForeinTypeWithoutStringer
func(t MyTypeWithStringer) String() string {
// ...
}
This is a solution that fits me best
type Person struct {
first string
last string
age int
}
type Product struct {
ProductID int
Name string
Description string
Price float64
}
func mapToStringSlice(slice interface{}, mapFunc func(interface{}) string) []string {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("mapToStringSlice() given a non-slice type")
}
ret := make([]string, s.Len())
for i:=0; i<s.Len(); i++ {
ret[i] = mapFunc(s.Index(i).Interface())
}
return ret
}
func main() {
persons := []Person{{
first: "A",
last: "g",
age: 20,
},{
first: "B",
last: "r",
age: 40,
},{
first: "C",
last: "",
age: 0,
}}
products := []Product{Product{ProductID: 1}, Product{ProductID: 2}}
personFirstNames := mapToStringSlice(persons, func(input interface{}) string {
return input.(Person).first
})
productIDs := mapToStringSlice(products, func(input interface{}) string {
return input.(Product).ProductID
})
}
I am writing a program which has several structs and functions to handle these structs differently. I am having a generic function which calls the required function based on the inputs. Is there a generic way to use the returned value from getStruct()?
package main
var X func(s []string) A
var Y func(s []string) B
type A struct {
Name string
Place string
}
type B struct {
Name string
Place string
Value string
}
func newA(s []string) A {
a := A{
Name: s[0],
Place: s[1],
}
return a
}
func newB(s []string) B {
a := B{
Name: s[0],
Place: s[1],
Value: s[2],
}
return a
}
func getStruct(t string) interface{} {
switch {
case t == "A":
return X
case t == "B":
return Y
default:
return //someStruct
}
}
func main() {
buildNewStruct := getStruct("A") //Lets assume "A" here is got as an argument
var strSlice = []string{"Bob", "US"}
buildNewStruct(strSlice) //How to do this operation?
//I am hoping to use buildNewStruct(strSlice) to dynamically call
//either of newA(strSlice) or newB(strSlice) function
}
I have tried looking at this and this the later is not exactly the same as my question.
Since I am new to go, I am not sure if something like this is possible.
you can use the reflect package to set the struct properties to the equivalent index positioned value from an []interface{} slice.
package main
import (
"fmt"
"log"
"reflect"
)
func main() {
var a A
err := decode(&a, []interface{}{"Name", "Place"})
log.Println(err)
log.Println(a)
}
func decode(dst interface{}, values []interface{}) error {
rvptr := reflect.ValueOf(dst)
if rvptr.Kind() != reflect.Ptr {
return fmt.Errorf("value must be ptr")
}
rv := rvptr.Elem()
if rv.NumField() < len(values) {
return fmt.Errorf("too many values")
}
if rv.NumField() > len(values) {
return fmt.Errorf("too few values")
}
rvalues := reflect.ValueOf(values)
for i := range values {
f := rv.FieldByIndex([]int{i})
f.Set(rvalues.Index(i).Elem())
}
return nil
}
type A struct {
Name string
Place string
}
type B struct {
Name string
Place string
Value string
}
prints
$ go run main.go
2019/11/21 17:00:17 <nil>
2019/11/21 17:00:17 {Name Place}
The problem is the return type for the function.
func newA(in []string) interface{} {...}
func newB(in []string) interface{} {...}
func getStruct(name string) func([]string) interface{} {
switch name {
case "A": return newA
case "B": return newB
}
return nil
}
func main() {
buildNewStruct := getStruct("A")
var strSlice = []string{"Bob", "US"}
str:=buildNewStruct(strSlice)
if a, ok:=str.(A); ok {
...
}
}
With this approach, even though you saved some code by calling a unified buildNewStruct(), you have to use type assertions to figure out what is returned from that function, so this may not make a lot of sense. It depends on your exact use case though.
I tried to append different functions that can be represented with the same interface.
Functions return different objects but same interface.
It failed with the error cannot use Test (value of type func() *Dog) as func() Animal value in argument to append (typecheck)
What should I do? Thanks in advance!
package main
type Dog struct {
Word string
}
type Cat struct {
Word string
}
func (d *Dog) Say() string {
return d.Word
}
func (c *Cat) Say() string {
return c.Word
}
type Animal interface {
Say() string
}
func main() {
funcs := []func() Animal{}
funcs = append(funcs, Test) // error| cannot use Test (value of type func() *Dog) as func() Animal value in argument to append (typecheck)
}
func Test() *Dog {
return &Dog{Word: "dog"}
}
func Test2() *Cat {
return &Cat{Word: "cat"}
}
Change your functions to have Animal as their return type. func() *Dog is not convertible to func() Animal, they are two separate data types.
Similar to how you can pass, say, int as interface{}, but not []int as []interface{}
The slice element and functions have different return types. Use anonymous functions to convert the function return values to the slice element return type.
funcs = append(funcs,
func() Animal { return Test() },
func() Animal { return Test2() })
for _, f := range funcs {
fmt.Println(f().Say())
}
Run it on the Playground.
Another option is to use the reflect package to call the function and convert the result to an Animal.
func makeAnimal(f interface{}) Animal {
// This function assumes that f is a function
// that returns a value that satisfies the
// Animal interface.
result := reflect.ValueOf(f).Call(nil)
return result[0].Interface().(Animal)
}
Use it like this:
funcs := []interface{}{}
funcs = append(funcs, Test, Test2)
for _, f := range funcs {
a := makeAnimal(f)
fmt.Println(a.Say())
}
Run it on the Playground.
The problem is that func () *Dog cannot converts to func() Animal.
If you don't wanna use reflection, you have to change the "funcs" type to []interface{} then cast each element of the slice into func() *Dog and simply call it, like this:
package main
import "fmt"
type Dog struct {
Word string
}
type Cat struct {
Word string
}
func (d *Dog) Say() string {
return d.Word
}
func (c *Cat) Say() string {
return c.Word
}
type Animal interface {
Say() string
}
func main() {
var funcs []interface{}
funcs = append(funcs, Test)
fmt.Println(funcs[0].(func() *Dog)().Say()) // prints "dog"
}
func Test() *Dog {
return &Dog{Word: "dog"}
}
func Test2() *Cat {
return &Cat{Word: "cat"}
}