Skip to content

Instantly share code, notes, and snippets.

@jotak
Last active March 26, 2019 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jotak/14aef925c4ab60a45766e9c47413d700 to your computer and use it in GitHub Desktop.
Save jotak/14aef925c4ab60a45766e9c47413d700 to your computer and use it in GitHub Desktop.
Missing some meta-programming in Go

One issue I'm currently facing in Go (looks like it's not an isolated problem) is the lack of meta-programming.

I want to create a simple function that turns any slice into a map, being provided a key-getter function.

I wish I could write something like this:

type KeySupplier<T> func(obj T) string

func Index<T>(arr []T, keySupplier KeySupplier<T>) map[string]T {
	indexed := make(map[string]T, len(arr))
	for _, obj := range arr {
		key := keySupplier(obj)
		indexed[key] = obj
	}
	return indexed
}

Of course, that doesn't compile. <T> is not a valid syntax, and there's no replacement for that. But OK, let's not try to be type-safe, remove the <T> and declare type T interface{}:

type T interface{}
type KeySupplier func(obj T) string

func Index(arr []T, keySupplier KeySupplier) map[string]T {
	indexed := make(map[string]T, len(arr))
	for _, obj := range arr {
		key := keySupplier(obj)
		indexed[key] = obj
	}
	return indexed
}

It compiles, yay!

Sadly it's almost unusable :-(

To illustrate, here is a test:

type XYZ struct {
	key string
	val int
}

func TestIndex(t *testing.T) {
	assert := assert.New(t)

	arr := []XYZ{
		XYZ{
			key: "abc",
			val: 1,
		},
		XYZ{
			key: "def",
			val: 2,
		},
	}
	supplier := func(obj XYZ) string {
		return obj.key
	}
	indexed := Index(arr, supplier)

	assert.Len(indexed, 2)
	assert.Equal(indexed["abc"].val, 1)
	assert.Equal(indexed["def"].val, 2)
}

This test doesn't compile, because we cannot use arr (type []XYZ) as type []T in argument to Index. This is explained here: https://github.com/golang/go/wiki/InterfaceSlice.

The suggested workaround is... to copy the slice of XYZ into a slice of interface{}. From the calling side of course, as my Index function has no idea about what the actual XYZ is. Something like that:

func TestIndex(t *testing.T) {
	assert := assert.New(t)

	arr := []XYZ{
		XYZ{
			key: "abc",
			val: 1,
		},
		XYZ{
			key: "def",
			val: 2,
		},
	}
	supplier := func(obj T) string {
		return obj.(XYZ).key
	}
	var interfaceSlice []T = make([]T, len(arr))
	for i, d := range arr {
		interfaceSlice[i] = d
	}

	indexed := Index(interfaceSlice, supplier)

	assert.Len(indexed, 2)
	assert.Equal(indexed["abc"].(XYZ).val, 1)
	assert.Equal(indexed["def"].(XYZ).val, 2)
}

This is:

  • Cumbersome
  • Ugly
  • Slow
  • Not type-safe

It reduces to 0 the interest of this API function, and probably same for any kind of generic API on collections. Because of that, the code produced is full of repetitions and noise. Sad :-(

Most other languages have a solution for that, and I'm not only thinking about Java or C++. There's generics, templates, macros, ... Not sure what would fit the best for Go, but there's definitely several approaches to tackle that in language design.

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