Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way to implement generic functions in Go

Let's say I want to write a function to check whether a predicate is matched for an element in a slice:

func IsIn(array []T, pred func(elt T) bool) bool {
    for _, obj := range array {
        if pred(obj) { return true;}
    }
    return false;
}

Obviously, the previous code won't compile, since T does not exist. I can replace it with some interface{} like this:

func IsIn(array[]interface{}, pred func(elt interface{}) bool) bool {
    ...
}

As I am happy to let the predicate perform the casting:

IsIn([]interface{}{1,2,3,4}, func(o interface{}) {return o.(int) == 3; });

But then, the function won't accept any array which is not of type []interface{}:

IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // DO NOT COMPILE

And similarly:

func IsIn(arr interface, pred func(o interface{}) bool) bool {
    for _, o := range arr.([]interface{}) { ... }
}
IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // PANICS AT RUNTIME (cannot cast []int to []interface)

The other alternative is to have typed functions for each array type:

IsInInt(arr []int, pred func(i int) bool) { ... }
IsInStr(arr []string, pred func(s string) bool) { ... }
...

But it seems like a LOT of code duplication.

Has anyone come up with an nice way to deal with such situations ?

EDIT

Thanks to jnml's fantastic tips on Go reflection, I think I have found a nice way to express these patterns, by converting every 'iterable' to a channel:

func iter(obj interface{}) chan interface{} {
    c := make(chan interface{})
    v := reflect.ValueOf(obj)
    if (v.Kind() == reflect.Array || v.Kind() == reflect.Slice) {
        go func() {
            for i := 0; i < v.Len(); i++ {
                c<-v.Index(i).Interface()
            }
            close(c)
        }()
    } else if v.Kind() == reflect.Chan {
        go func() {
            x, ok := v.Recv()
            for ok {
                c<-x.Interface()
                x,ok = v.Recv()
            }
            close(c)
        }()
    } else if (... whatever iteration protocol you have ...) {
    } else {
        panic("Cannot iterate !")
    }
    return c;
}

With my initial example rewritten using it on the playground.

Thanks a lot to jnml and ANisus for helping out !

like image 263
val Avatar asked Aug 07 '13 10:08

val


People also ask

How do generics work in Go?

With generics, you can declare and use functions or types that are written to work with any of a set of types provided by calling code. In this tutorial, you'll declare two simple non-generic functions, then capture the same logic in a single generic function.

Does Golang have generics yet?

Generics allows programmers to write general, generic code that works with different types and enables code reuse. Golang (Go), however, does not support generics, yet.

Why is there no generics in Go?

As we are all aware Go has no Generics, it was designed with simplicity in mind and Generics as mentioned above is considered to add complexity to the language. The same goes for Inheritance, Polymorphism and some other features that the top object-oriented languages showed when Go was created.

When did Go Add generics?

Downloadable at go. dev as of March 15, Go 1.18 introduces support for generic code using parameterized types. Generics has been called the most significant change to the Go programming language since the release of Go 1.0 in 2012. It was also the most-requested feature by Go programmers.


2 Answers

For example:

package main

import (
        "fmt"
        "reflect"
)

func checkSlice(slice interface{}, predicate func(reflect.Value) bool) bool {
        v := reflect.ValueOf(slice)
        if v.Kind() != reflect.Slice {
                panic("not a slice")
        }

        for i := 0; i < v.Len(); i++ {
                if predicate(v.Index(i)) {
                        return true
                }
        }

        return false
}

func main() {
        a := []int{1, 2, 3, 4, 42, 278, 314}
        fmt.Println(checkSlice(a, func(v reflect.Value) bool { return v.Int() == 42 }))

        b := []float64{1.2, 3.4, -2.5}
        fmt.Println(checkSlice(b, func(v reflect.Value) bool { return v.Float() > 4 }))
}

Playground


Output:

true
false
like image 101
zzzz Avatar answered Oct 27 '22 10:10

zzzz


I can't say if it is the most idiomatic. But one idiomatic solution would be to do like in the sort package; to define an interface for the array:

type Interface interface {
    Len() int
    Equal(i int, v interface{}) bool
}

func IsIn(array Interface, value interface{}) bool {
    for i := 0; i < array.Len(); i++ {
    if array.Equal(i, value) {
            return true
        }
    }
    return false;
}

As long as your array implements this interface, you can use IsIn().

Working example can he found here

like image 38
ANisus Avatar answered Oct 27 '22 11:10

ANisus