Skip to content

Instantly share code, notes, and snippets.

@awalterschulze
Last active September 24, 2017 14:47
Show Gist options
  • Save awalterschulze/e3999f8cfa29b246c35a651a2be4d121 to your computer and use it in GitHub Desktop.
Save awalterschulze/e3999f8cfa29b246c35a651a2be4d121 to your computer and use it in GitHub Desktop.
Go Experience Report: Generic functions cannot be passed as values

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment