awalterschulze / goderive

Derives and generates mundane golang functions that you do not want to maintain yourself
Apache License 2.0
1.24k stars 43 forks source link

Go Experience Report: Generic functions cannot be passed as values #10

Open awalterschulze opened 7 years ago

awalterschulze commented 7 years ago

Generic functions cannot be passed as values

While developing GoDerive, a code generator for Go, I ran into the following problem.

I could not infer the type of the input arguments of a function, if that function's input argument types are not specified and the function is not being called right away.

I wanted to write:

func TestCurriedEqualCompileError(t *testing.T) {
    curriedEqual := deriveCurry(deriveEqual)
    if !curriedEqual(&User{})(&User{}) {
        t.Fatalf("not equal")
    }
}

, where functions that start with the prefix "derive" are generated.

This can be fixed by rewriting it as:

func TestCurriedEqualCompileError(t *testing.T) {
    curriedEqual := deriveCurry(func(a, b *User) bool {
        return deriveEqual(a, b)
    })
    if !curriedEqual(&User{})(&User{}) {
        t.Fatalf("not equal")
    }
}

, but this is not ideal.

I tried to find how Go does this for it's generic functions, like copy, but I found that this limitation is actually part of the Go specification:

The built-in functions do not have standard Go types, so they can only appear in call expressions; they cannot be used as function values.

https://golang.org/ref/spec#Built-in_functions

Here is an example to showcase the limitation:

func twoslice(a, b []int, f func(c, d []int) int) {
    f(a, b)
}

func main() {
    a, b := []int{1,2}, []int{3, 4} 
    twoslice(a, b, copy)
    fmt.Println("%v, %v", a, b)
}

https://play.golang.org/p/ghITepOq6l

This gives the following error:

use of builtin copy not in function call

I am not saying this is an easy problem to solve, but it stops GoDerive from generating functions that can be passed as values.

In the meantime I have updated my code generator's deriveEqual function to be able to generate, given only the first argument.

This allows us to now write:

func TestCurriedEqualCompileError(t *testing.T) {
    if !deriveEqual(&User{})(&User{}) {
        t.Fatalf("not equal")
    }
}

, but this will not be possible for all such usecases.

This is just my experience of trying to generate generic functions in Go and one of the limitations I ran into.