Basic + Slice + Map Type Compatible Generics? - go

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

Related

Using a Pointer to a Function for multiple types in GoLang

I have a lot (20+) "Enum Types" of the following form. What I'd like to do is list all the Enum types by name in a struct (see below) and a pointer to a function that returns all of the types. See below for an abbreviated sample.
package main
type Fruit int
const (
unknownFruit Fruit = iota // must be first
Apple
Banana
fruitDone
)
func FruitTypes() []Fruit {
var res []Fruit
for typ := unknownFruit + 1; typ < fruitDone; typ++ {
res = append(res, typ)
}
return res
}
type Car int
const (
unknownCar Car = iota // must be first
BMW
Mercedes
doneCar
)
func CarTypes() []Car {
var res []Car
for typ := unknownCar + 1; typ < doneCar; typ++ {
res = append(res, typ)
}
return res
}
func main() {
enumTypes := []struct {
Name string
Length int
ConvertFunc func(int) string
}{
{Name: "Fruit", Length: int(doneFruit), ConvertFunc: ConvertEnum[Fruit]},
{Name: "Car", Length: int(doneCar), ConvertFunc: ConvertEnum[Car]},
}
for _, enumType := range enumTypes {
fmt.Println(enumType.Name)
for i := 0; i < enumType.Length; i++ {
fmt.Printf(" -- %s", enumType.ConvertFunc(i))
}
}
}
func ConvertEnum[T any](raw int) string {
return fmt.Sprintf("%v", T(raw)) // #2 - This will not convert
}
Unfortunately, I cannot seem to declare the function pointer properly. What's the correct return type?
(As an aside, is there a way to declare an interface for these enum types and/or use generics so that I don't have to have FruitType(), CarType(), PetType()...
=================
UPDATE - thanks to #Woody1193, I feel like I'm much closer. I'm now using the fact that enums are sequential numbers, so I don't really need to pass in any details other than the type so I can cast it back inside the loop (see marker #1 above).
However, in the above, I get:
./main.go:55:64: cannot use EnumType[Fruit] (value of type func(done Fruit) []Fruit) as type func() []int in struct literal
./main.go:56:60: cannot use EnumType[Car] (value of type func(done Car) []Car) as type func() []int in struct literal
./main.go:62:43: too many arguments in call to typ.TypeFunction
=================
Update #2 - I've tried to take a different angle, and just pass in the conversion function, to get it out of the struct. (See above). But now it won't let me cast it :(
./main.go:68:31: cannot convert raw (variable of type int) to type T
The issue you're having here is that neither Car nor Fruit is actually an integer, so TypesFunction will not accept CarTypes or FruitTypes. Generics will not work in this situation because declaring a slice of such objects would require them to all have the same type parameter, which means your code still wouldn't work.
Therefore, your best option is to modify CarTypes and FruitTypes so that they have the same signature, func() []int:
func FruitTypes() []int {
var res []int
for typ := unknownFruit + 1; typ < fruitDone; typ++ {
res = append(res, typ)
}
return res
}
func CarTypes() []int {
var res []int
for typ := unknownCar + 1; typ < doneCar; typ++ {
res = append(res, typ)
}
return res
}
Of course, this will mean that you'll have to use type casting when you want the specific type of enum, but the code will work.
As an aside, you can use generics to generate the function itself:
func EnumType[T ~int](done T) []int {
var res []int
for typ := T(1); typ < done; typ++ {
res = append(res, typ)
}
return res
}
func ConvertEnum[T ~int](raw ...int) []T {
res := make([]T, len(raw))
for i, typ := range raw {
res[i] = T(typ)
}
return res
}
enumTypes := []struct {
Name string
TypesFunction func() []int
}{
{Name: "Fruit", TypesFunction: EnumType[Fruit]},
{Name: "Car", TypesFunction: EnumType[Car]},
}

Is there a nice way to get length of slice elem in map regardless of its concrete type?

For example, I have two maps, namely map[string][]structAand map[string][]int.
I want to iterate the map and print out length of every slice, can I implement this in a single function?
I tried to define an interface and implement it in two kinds of slices, but the compiler just cannot do the type cast.
type Container interface{
Len() int
}
type Slice1 []int
func (s Slice1) Len() int {
return len(s)
}
type Slice2 []StructA
func (s Slice2) Len() int {
return len(s)
}
func iterate(input map[string]Container) {
for key, value := range input {
log.Printf("key:%s, lenght:%d", key, value.Len())
}
}
func main() {
// cannot do the type cast
iterate(map[string]Slice1{})
iterate(map[string]Slice2{})
}
"can I implement this in a single function?"
Yes, but the way you called the function should be changed. Convert your maps to map[string]Container and then call the iterate
func main() {
// cannot do the type cast
map1 := map[string][]int{
`key`: []int{1,2,3},
}
map1Container := make(map[string]Container)
for key, value := range map1 {
map1Container[key] = Slice1(value)
}
map2 := map[string][]StructA{
`key1`: []StructA{{}, {}},
}
map2Container := make(map[string]Container)
for key, value := range map2 {
map2Container[key] = Slice2(value)
}
iterate(map1Container)
iterate(map2Container)
}
Output:
2021/07/06 12:15:25 key:key, lenght:3
2021/07/06 12:15:25 key:key1, lenght:2
iterate function expected map[string]Container type parameter. So you can initiate that with Container type and inside the map with different keys, you can include different Container's implementations.
func main() {
// cannot do the type cast
iterate(map[string]Container{
`ke1`: Slice1([]int{
1, 2, 3,
}),
`ke2`: Slice2([]StructA{
{},
{},
}),
})
}
Output:
2021/07/06 11:57:55 key:ke1, lenght:3
2021/07/06 11:57:55 key:ke2, lenght:2

Hashing reflect.Type

I'm trying to find a quick way of performing comparisons between two []reflect.Type. Right now I have the following:
func Equal(left, right []reflect.Type) bool {
if len(left) != len(right) {
return false
}
for i := 0; i < len(left); i++ {
if left[i] != right[i] {
return false
}
}
return true
}
Most of the slices don't change. So if I can find a way to hash them, I'd get a huge perf boost.
Background
I'm trying to (for fun) implement a form of function overloading in Go using the reflect package. The first thing I did is to convert each specialised/overloaded function into a signature type.
type Signature struct {
Variadic bool
In, Out []reflect.Type
}
The idea is that, when the overloaded function gets called, I'll convert the arguments into a slice of reflect.Type and then find a Signature where the In types match.
This works, but for each comparison, it's a linear scan which is pretty slow. If I could hash the slice of []reflect.Type I could stick that in a map and get constant time lookups.
I ended up abusing the built-in map to assign unique ids to each reflect.Type. Then I hash those using djb2.
type TypeCode struct {
seq int64
codes map[reflect.Type]int64
}
func (td *TypeCode) TypeID(t reflect.Type) int64 {
if code, ok := td.codes[t]; ok {
return code
}
td.seq++
td.codes[t] = td.seq
return td.seq
}
func (td *TypeCode) SliceTypeID(tt []reflect.Type) int64 {
id := int64(5381)
for _, t := range tt {
id = ((id << 5) + id) + td.TypeID(t)
}
return id
}
edit: I switched to a string based approach which is less efficient, but removes any potential for collisions.
type TypeCode struct {
seq int64
codes map[reflect.Type]string
}
func (td *TypeCode) TypeID(t reflect.Type) string {
if code, ok := td.codes[t]; ok {
return code
}
td.seq++
id := strconv.FormatInt(td.seq, 10)
td.codes[t] = id
return id
}
func (td *TypeCode) SliceTypeID(tt []reflect.Type) string {
ids := make([]string, len(tt))
for i := 0; i < len(tt); i++ {
ids[i] = td.TypeID(tt[i])
}
return strings.Join(ids, ".")
}

Golang polymorphic parameters and returns

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.

Generic variadic argument in Go?

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?
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.
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.
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
}

Resources