Is it possible to iterate over a generic type constrained by a union type constraint? [duplicate] - go

I am testing out generics in go 1.18 and took a look at this example.
I would like to recreate that example but instead be able to pass in a slice of int or slice of float instead, and in the function I'll just sum up everything in the slice.
This is when I ran into some issues just iterating the slice. This is what I tried:
package main
import "fmt"
// NumberSlice constraint
type NumberSlice interface {
[]int64 | []float64
}
func add[N NumberSlice](n N) {
// want: to range over n and print value of v
for _, v := range n {
fmt.Println(v)
}
}
func main() {
ints := []int64{1, 2}
add(ints)
}
I got the error:
cannot range over n (variable of type N constrained by NumberSlice) (N has no core type)
How do I accomplish this?

A core type, for an interface (including an interface constraint) is defined as follows:
An interface T has a core type if one of the following conditions is
satisfied:
There is a single type U which is the underlying type of all types in the type set of T
or the type set of T contains only channel types with identical element type E, and all directional channels have the same direction.
Your interface constraint has no core type, because it has two underlying types: []int64 and []float64.
Therefore you can't use it where a core type is required. Notably range and make.
You can change the interface to require the base types, and then specify the slice in the function signature:
// still no core type...
type Number interface {
int64 | float64
}
// ...but the argument will be instantiated with either int64 or float64
func add[N Number](n []N) {
for _, v := range n {
fmt.Println(v)
}
}
This also works, but it's way more verbose:
type NumberSlice[N int64 | float64] interface {
// one core type []N
~[]N
}
func add[S NumberSlice[N], N int64 | float64](n S) {
for _, v := range n {
fmt.Println(v)
}
}

Could something like this work for you?
package main
import "fmt"
type NumberOrFloat interface {
int64 | float64
}
func add[N NumberOrFloat](n []N) {
for _, v := range n {
fmt.Println(v)
}
}
func main() {
ints := []int64{1, 2}
add(ints)
}
The difference here is that you define type constraints on array elements (not on array types): []N

Related

generics with a T type and a named function type that takes T

I have a module that defines a bunch of types
type Thing1 struct {}
type Thing2 struct {}
and named functions types that take the types above as arguments
type T1 func(t *Thing1)
type T2 func(t *Thing2)
Then it defines a map using these function types
var (
ModThing1 = map[string]T1{ ... }
ModThing2 = map[string]T2{ ... }
)
In my app that uses this module, I would like use a generic for Thing1 and Thing2
Something like:
func do[T any](in *T, inMap map[string]func(in *T)) {
for _, val := range inMap {
val(in)
}
}
...
do[mod.Thing1](&mod.Thing1{}, mod.ModThing1)
Of course the problem is that Go wont allow this because the type of the value of the map is not the same as mod.ModThing1 value type. func(in *T)) vs mod.T1
Is there a way to get this to work?
In the function do, declare an additional type parameter F with an approximated constraint that also references T. You take advantage of T1 and T2 having similar underlying types. You don't even have to explicitly instantiate do's type arguments, both can be inferred.
func main() {
do(&foo.Thing1{}, foo.ModThing1)
do(&foo.Thing2{}, foo.ModThing2)
}
func do[T any, F ~func(*T)](in *T, inMap map[string]F) {
for _, val := range inMap {
val(in)
}
}
Playground: https://go.dev/play/p/wiWWzXVDG7v
If you have control over the imported package, another solution is to use a generic function type instead of T1 and T2. The principle behind this abstraction is the same. In fact, here you can see more clearly why the first solution works:
type F[T any] func(t *T)
// instead of
// type T1 func(t *Thing1)
// type T2 func(t *Thing2)
Then you declare the map variables with specific instantiations of F[T]:
var (
ModThing1 = map[string]F[Thing1]{ ... }
ModThing2 = map[string]F[Thing2]{ ... }
)
Then in the do function you instantiate F with the type parameter:
func do[T any](in *T, inMap map[string]F[T]) {
for _, val := range inMap {
val(in)
}
}
Playground: https://go.dev/play/p/ITBqiqjjVUz
By the way, you can also abstract the map type:
type M[T any] map[string]F[T]
func do[T any](in *T, inMap M[T]) {
for _, val := range inMap {
val(in)
}
}

Generics range over union of maps

There is a simple example of use of generics in which we want to copy a map
package main
import "fmt"
type myMap interface {
map[string]int | map[string]float64
}
func copyMap[T myMap](m T) T {
newMap := make(T)
for key, elem := range m {
newMap[key] = elem
}
return newMap
}
func main() {
m := map[string]int{"seven": 7}
fmt.Println(copyMap(m))
}
demo here
This code fails to compile returning error
./prog.go:12:17: invalid argument: cannot make T: no core type
./prog.go:13:25: cannot range over m (variable of type T constrained by myMap) (T has no core type)
./prog.go:14:18: invalid operation: cannot index m (variable of type T constrained by myMap)
How can I circumvent this issue and have a working generic copyMap function working for types map[string]int and map[string]float64?
Do
func copyMap[T ~map[string]V, V any](m T) T {/* ... */}
demo
Or indeed just use https://cs.opensource.google/go/x/exp/+/062eb4c6:maps/maps.go;l=65 (who uses a similar construct) as #jubObs mentioned.

Having multiple options in a case and then a loop within the case go

I have a function that accepts an interface{} I then do a switch, case on the type and if it's a slice I want to iterate over the elements. The issue I'm having is I can't have multiple options in the case selector, for example I can't seem to have []int, []float32 and then do a range over the values.
What I want to do is something like this
func digestCollection(obj interface{}) ([]byte, error) {
switch v := obj.(type) {
case []int64, []float64:
for _, values := range v {
// do something with v whether its an int or float
}
}
}
But I get an error saying I can't iterate over interface.
In a type switch, if there is a single type case, then v is of that type:
switch v:=obj.(type) {
case []int64:
// Here, v is []int64
case []float64:
// here, v is []float64
}
However if there are multiple cases, or if it is the default case, then the type of v is the type of obj:
switch v:=obj.(type) {
case []int64,[]float64:
// Here, type of v is type of obj
because v cannot have a definite type if it is either an int array or a float64 array. The code generated for the two would be different.
You can try using reflection to go through the array, or write two loops, one for int and one for float64.

struct type as map key [duplicate]

This question already has an answer here:
golang how can I use struct name as map key
(1 answer)
Closed 9 months ago.
We have a following function:
func (h *Handler) Handle(message interface{}) error {
//here there is a switch for different messages
switch m := message.(type) {
}
}
This signature is given and can't be changed. There are around 20 different message types the handler processes.
Now, there are some of these messages (around 4) which need special post-processing. In a different package.
Thus, I am thinking to do this like this:
func (h *Handler) Handle(message interface{}) error {
//here there is a switch for different messages
switch m := message.(type) {
}
//only post-process if original message processing succeeds
postProcessorPkg.Process(message)
}
Now, in the Process function, I want to quickly lookup if the message type is indeed of the ones we need postprocessing for. I don't want to do a switch again here. There are many handlers, in different packages, with varying amount of message types, and it should be generic.
So I was thinking of registering the message type in the postprocessor and then just do a lookup:
func (p *Postprocessor) Register(msgtype interface{}) {
registeredTypes[msgtype] = msgtype
}
and then
func (p *Postprocessor) Process(msgtype interface{}) error {
if ok := registeredTypes[msgtype]; !ok {
return errors.New("Unsupported message type")
}
prop := GetProp(registeredTypes[msgtype])
doSmthWithProp(prop)
}
This will all not work now because I can only "register" instances of the message, not the message type itself, as far as I know. Thus the map would only match a specific instance of a message, not its type, which is what I need.
So I guess this needs redesign. I can completely ditch the registering and the map lookup, but
I can't change the Handle function to a specific type (signature will need to remain message interface{}
I would like to avoid to have to use reflect, just because I will have a hard time defending such a solution with some colleagues.
As there is no possibility to set a type as the map key, I finally decided to implement the following solution, which is based on #Chrono Kitsune 's solution:
type Postprocess interface {
NeedsPostprocess() bool
}
type MsgWithPostProcess struct {}
func (p *MsgWithPostProcess) NeedsPostprocess() bool {
return true
}
type Msg1 struct {
MsgWithPostProcess
//other stuff
}
type Msg2 struct {
MsgWithPostProcess
//other stuff
}
type Msg3 struct {
//no postprocessing needed
}
func (p *Postprocessor) Process(msgtype interface{}) error {
if _, ok := msgtype.(Postprocess); ok {
//do postprocessing
}
}
As of my simple test I did, only Msg1 and Msg2 will be postprocessed, but not Msg3, which is what I wanted.
This question was the first hit I found on Google but the title is somewhat misleading. So I'll leave this here to add some food for thought with the title of the question in mind.
First, the issue with maps is that its key must be a comparable value. This is why for example a slice cannot be used is a map key. A slice is not comparable and is therefore not allowed. You can use an array (fixed sized slice) but not a slice for the same reason.
Second, you have in the reflect.TypeOf(...).String()a way to get a canonical string representation for types. Though it is not unambiguous unless you include the package path, as you can see here.
package main
import (
"fmt"
s2 "go/scanner"
"reflect"
s1 "text/scanner"
)
type X struct{}
func main() {
fmt.Println(reflect.TypeOf(1).String())
fmt.Println(reflect.TypeOf(X{}).String())
fmt.Println(reflect.TypeOf(&X{}).String())
fmt.Println(reflect.TypeOf(s1.Scanner{}).String())
fmt.Println(reflect.TypeOf(s2.Scanner{}).String())
fmt.Println(reflect.TypeOf(s1.Scanner{}).PkgPath(), reflect.TypeOf(s1.Scanner{}).String())
fmt.Println(reflect.TypeOf(s2.Scanner{}).PkgPath(), reflect.TypeOf(s2.Scanner{}).String())
}
int
main.X
*main.X
scanner.Scanner
scanner.Scanner
text/scanner scanner.Scanner
go/scanner scanner.Scanner
https://play.golang.org/p/NLODZNdik6r
With this information, you can (if you feel so inclined) create a map which let's go from a reflect.Type to a key and back again, like this.
package main
import (
"fmt"
s2 "go/scanner"
"reflect"
s1 "text/scanner"
)
type TypeMap struct {
m []reflect.Type
}
func (m *TypeMap) Get(t reflect.Type) int {
for i, x := range m.m {
if x == t {
return i
}
}
m.m = append(m.m, t)
return len(m.m) - 1
}
func (m *TypeMap) Reverse(t int) reflect.Type {
return m.m[t]
}
type X struct{}
func main() {
var m TypeMap
fmt.Println(m.Get(reflect.TypeOf(1)))
fmt.Println(m.Reverse(0))
fmt.Println(m.Get(reflect.TypeOf(1)))
fmt.Println(m.Reverse(0))
fmt.Println(m.Get(reflect.TypeOf(1)))
fmt.Println(m.Reverse(0))
fmt.Println(m.Get(reflect.TypeOf(X{})))
fmt.Println(m.Reverse(1))
fmt.Println(m.Get(reflect.TypeOf(&X{})))
fmt.Println(m.Reverse(2))
fmt.Println(m.Get(reflect.TypeOf(s1.Scanner{})))
fmt.Println(m.Reverse(3).PkgPath(), m.Reverse(3))
fmt.Println(m.Get(reflect.TypeOf(s2.Scanner{})))
fmt.Println(m.Reverse(4).PkgPath(), m.Reverse(4))
}
0
int
0
int
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner
In the above case I'm assuming that N is small. Also note the use of the identity of reflect.TypeOf, it will return the same pointer for the same type on subsequent calls.
If N is not small, you may want to do something a bit more complex.
package main
import (
"fmt"
s2 "go/scanner"
"reflect"
s1 "text/scanner"
)
type PkgPathNum struct {
PkgPath string
Num int
}
type TypeMap struct {
m map[string][]PkgPathNum
r []reflect.Type
}
func (m *TypeMap) Get(t reflect.Type) int {
k := t.String()
xs := m.m[k]
pkgPath := t.PkgPath()
for _, x := range xs {
if x.PkgPath == pkgPath {
return x.Num
}
}
n := len(m.r)
m.r = append(m.r, t)
xs = append(xs, PkgPathNum{pkgPath, n})
if m.m == nil {
m.m = make(map[string][]PkgPathNum)
}
m.m[k] = xs
return n
}
func (m *TypeMap) Reverse(t int) reflect.Type {
return m.r[t]
}
type X struct{}
func main() {
var m TypeMap
fmt.Println(m.Get(reflect.TypeOf(1)))
fmt.Println(m.Reverse(0))
fmt.Println(m.Get(reflect.TypeOf(X{})))
fmt.Println(m.Reverse(1))
fmt.Println(m.Get(reflect.TypeOf(&X{})))
fmt.Println(m.Reverse(2))
fmt.Println(m.Get(reflect.TypeOf(s1.Scanner{})))
fmt.Println(m.Reverse(3).PkgPath(), m.Reverse(3))
fmt.Println(m.Get(reflect.TypeOf(s2.Scanner{})))
fmt.Println(m.Reverse(4).PkgPath(), m.Reverse(4))
}
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner
https://play.golang.org/p/2fiMZ8qCQtY
Note the subtitles of pointer to type, that, X and *X actually are different types.

generic function to get size of any structure in Go

I am writing a generic function to get the size of any type of structure, similar to sizeof function in C.
I am trying to do this using interfaces and reflection but I'm not able to get the correct result. Code is below:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
type myType struct {
a int
b int64
c float32
d float64
e float64
}
info := myType{1, 2, 3.0, 4.0, 5.0}
getSize(info)
}
func getSize(T interface{}) {
v := reflect.ValueOf(T)
const size = unsafe.Sizeof(v)
fmt.Println(size)
}
This code returns wrong result as 12. I am very new to Go, kindly help me on this.
You're getting the size of the reflect.Value struct, not of the object contained in the interface T. Fortunately, reflect.Type has a Size() method:
size := reflect.TypeOf(T).Size()
This gives me 40, which makes sense because of padding.
Go 1.18
With Go 1.18 you can use a generic function with unsafe.Sizeof:
func getSize[T any]() uintptr {
var v T
return unsafe.Sizeof(v)
}
Note that this will be more performant than using reflect, but it will introduce unsafe in your code base — some static analysis tools may give warnings about that.
However if your goal is to improve code reuse or get sizes at run time (read on for the solution to that), this won't help much because you still need to call the function with proper instantiation:
type myType struct {
a int
b int64
c float32
d float64
e float64
}
func main() {
fmt.Println(getSize[myType]())
}
You might get the most out of this when used as part of some other generic code, e.g. a generic type or function where you pass a type param into getSize. Although if you have the argument v this is equivalent to calling unsafe.Sizeof(v) directly. Using a function could be still useful to hide usage of unsafe. A trivial example:
func printSize[T any](v T) {
// (doing something with v)
// instantiate with T and call
s := getSize[T]()
// s := unsafe.Sizeof(v)
fmt.Println(s)
}
Otherwise you can pass an actual argument to getSize. Then type inference will make it unnecessary to specify the type param. This code perhaps is more flexible and allows you to pass arbitrary arguments at runtime, while keeping the benefits of avoiding reflection:
func getSize[T any](v T) uintptr {
return unsafe.Sizeof(v)
}
func main() {
type myType struct {
a int
b int64
c float32
d float64
e float64
}
info := myType{1, 2, 3.0, 4.0, 5.0}
// inferred type params
fmt.Println(getSize(info)) // 40
fmt.Println(getSize(5.0)) // 8
fmt.Println(getSize([]string{})) // 24
fmt.Println(getSize(struct {
id uint64
s *string
}{})) // 16
}
Playground: https://go.dev/play/p/kfhqYHUwB2S

Resources