Cannot use golang generics variable in function call - go

I am trying to understand generics implementation in go1.18. In my test example, I am storing a series of test cases and attempting to call a function variable. Unfortunately, I am getting an error in the EvalCases function when I try to use the variable tc.input, I receive the following error:
cannot use input (variable of type T constrained by Inputer) as type
string in argument to fn
Why am I receiving that error, and how do I fix it?
import (
"fmt"
"strconv"
)
type BoolCase func(string) bool
type Inputer interface {
int | float64 | ~string
}
type Wanter interface {
Inputer | bool
}
type TestCase[T Inputer, U Wanter] struct {
input T
want U
}
type TestConditions[T Inputer, U Wanter] map[string]TestCase[T, U]
// IsNumeric validates that a string is either a valid int64 or float64
func IsNumeric(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
}
func EvalCases[T Inputer, U Wanter](cases TestConditions[T, U], fn BoolCase) {
for name, tc := range cases {
input := T(tc.input)
want := tc.want
// Error: cannot use input (variable of type T constrained by Inputer) as type string in argument to fn
got := fn(input)
fmt.Printf("name: %-20s | input: %-10v | want: %-10v | got: %v\n", name, input, want, got)
}
}
func main() {
var cases = TestConditions[string, bool]{
"empty": {input: "", want: false},
"integer": {input: "123", want: true},
"float": {input: "123.456", want: true},
}
fn := IsNumeric
EvalCases(cases, fn)
}

Why am I receiving that error
Because fn is a BoolFunc, which is a func(string) bool, thus requires a string as parameter, but input is of type T. Furthermore, by your definition, T satisfies the Inputer constraint, and thus can also assume type int, float64 or any non-string type which has string as its underlying type (~string) none of which implicitly cast to string.
how do I fix it?
You need to change the definition of BoolCase to itself have a generic type parameter for its one parameter. You could constraint it to Inputer, but you could also use any (interface{}).
type BoolCase[T any] func(T) bool
Then make sure to serve this generic type parameter in the signature of function EvalCases:
func EvalCases[T Inputer, U Wanter](cases TestConditions[T, U], fn BoolCase[T])
https://go.dev/play/p/RdjQXJ0WpDh

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)
}
}

Iterate over a []rune | string passed in generics

I am working with generics with this constrained rule:
type LineParser[T []rune | string] struct {
}
And I have this generic method of that struct:
func (it *LineParser[T]) Parser(line T)
Inside of that method I want to iterate the line but I am getting this error:
invalid operation: cannot slice line (variable of type T constrained by []rune|string): T has no core type
any suggestions?
Convert the line value to a []rune value before iterating. This way, every instance of the method will iterate over the same type.
type LineParser[T []rune | string] struct {}
func (it *LineParser[T]) Parser(line T) {
for _, r := range []rune(line) {
// do something with the next rune
_ = r
}
}

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

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

Get type parameter from a generic struct using reflection

Imagine I have the following struct:
type MyGeneric[T string | int] struct {
}
I want to check whether the generic used to instantiate that struct was a string or a int when creating a new MyGeneric.
myGenericString := MyGeneric[string]{}
myGenericString.canHandle("hello") -> should return true
myGenericString.canHandle(8) -> should return false
func (mG MyGeneric[T]) canHandle(value any) bool {
// how to get what T is the same type as value
}
Just instantiate the T directly to get its value.
type MyGeneric[T any] struct {
}
func (mG MyGeneric[T]) canHandle(value any) bool {
var t T
tt := reflect.TypeOf(t)
vt := reflect.TypeOf(value)
fmt.Printf("-> %v == %v\n", tt, vt)
return tt == vt
}
type empty struct{}
func main() {
fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(""))
fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(1))
fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[struct{}]{}))
fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[empty]{}))
}
Output:
-> string == string
true
-> string == int
false
-> main.MyGeneric[struct {}] == main.MyGeneric[struct {}]
true
-> main.MyGeneric[struct {}] == main.MyGeneric[main.empty]
false
If you are worried about T allocating too much unused stack, make it an array instead:
var tArr [0]T
tt := reflect.TypeOf(tArr).Elem()
It hasn't been implemented yet. There is an open proposal about adding the necessary methods to reflect.Type.
The current workaround as of Go 1.19 is to parse the string obtained from TypeOf. Something like this:
var r = regexp.MustCompile("[A-Za-z0-9_]+\\.[A-Za-z0-9_]+\\[(.*)\\]")
func (mG MyGeneric[T]) typeParam(value any) {
tname := reflect.TypeOf(mG).String() // this is `main.MyGeneric[string]`
match := r.FindStringSubmatch(tname)
fmt.Println(match[1]) // string
}
This if the goal is just to obtain the name of the type parameter. It's not great, as it depends on the type's string representation. On the bright side, it doesn't force you to think about what happens if you instantiate T with interfaces.
If you need to do further computations with the type of T, e.g. compare it to other types etc. #SOFe’s answer provides a solution that doesn’t depend on arbitrary string representations.
However watch out for T instantiated with interfaces: see also In Golang, how to compare interface as generics type to nil?
Another solution is to add a placeHolder field to your struct, that can be used to get its type via type switch to avoid reflect:
type MyGeneric[T string | int] struct {
placeHolder T
}
func (mG MyGeneric[T]) canHandle(value any) bool {
switch t1 := any(p.placeHolder).(type) {
case any:
switch t2 := value.(type) {
case any:
return t1 == t2
}
}
return false
}

Does type assertion change the value in go?

Go newbie here.
I have a map where the key arguments should be []string.
However, if I try to use the value directly arguments := m["arguments"] it doesn't seem to be the right type. When used later to append to another slice with arguments... I get Cannot use 'arguments' (type interface{}) as type []string.
I fixed this by chaning the assignment to a type check arguments, _ := m["arguments"].([]string). That works, but I'm not sure why. Is type assertion doing conversion as well?
The full example is below:
import (
"github.com/fatih/structs"
"strings"
)
var playbookKeyDict = map[string]string{
"Playbook": "",
"Limit" : "--limit",
"ExtraVars" : "--extra-vars",
}
type Playbook struct {
Playbook string `json:"playbook" xml:"playbook" form:"playbook" query:"playbook"`
Limit string `json:"limit" xml:"limit" form:"limit" query:"limit"`
ExtraVars string `json:"extra-vars" xml:"extra-vars" form:"extra-vars" query:"extra-vars"`
Arguments []string `json:"arguments" xml:"arguments" form:"arguments" query:"arguments"`
Args []string
}
func (p *Playbook) formatArgs() {
// is it worth iterating through directly with reflection instead of using structs import?
// https://stackoverflow.com/questions/21246642/iterate-over-string-fields-in-struct
m := structs.Map(p)
// direct assignment has the wrong type?
// arguments := m["arguments"]
arguments, _ := m["arguments"].([]string)
delete(m, "arguments")
for k, v := range m {
// Ignore non-strings and empty strings
if val, ok := v.(string); ok && val != "" {
key := playbookKeyDict[k]
if key == "" {
p.Args = append(p.Args, val)
} else {
p.Args = append(p.Args, playbookKeyDict[k], val)
}
}
}
p.Args = append(p.Args, arguments...)
}
Type assertion is used to get a value wrapped around using interface.
m := structs.Map(p)
Map(v interface{}){}
Map function is actually taking interface as its argument in the case stated. It is wrapping the type which is []string and its underlying value which is slice. The type can be checked using Relection reflect.TypeOf().
func TypeOf(i interface{}) Type
According to Russ Cox blog on Interfaces
Interface values are represented as a two-word pair giving a pointer
to information about the type stored in the interface and a pointer to
the associated data.
As specified in Golang spec
For an expression x of interface type and a type T, the primary
expression
x.(T)
asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
For the error part:-
Cannot use 'arguments' (type interface{}) as type []string
We first needs to get the underlying value of type []string from interface using type assertion.

Resources