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.