Reputation: 5648
I have the below go code and I am trying to do a recursion. However, the go compiler is complaining of a cyclic reference. Of course I have a cyclic reference; it's a recursion.
I have tried moving the recursion into the main function and out. Same issue. How can I achieve this recursion? The error line is: return lookup(deref.Interface()) // recursion error 'cyclic definition'
christianb@christianb-mac terraform-provider% go build
# github.com/terraform-provider/pkg/foo/repositories.go:773:5: initialization loop:
/Users/christianb/dev/terraform-provider/pkg/foo/repositories.go:773:5: lookup refers to
/Users/christianb/dev/terraform-provider/pkg/foo/repositories.go:774:18: glob..func6.1 refers to
/Users/christianb/dev/terraform-provider/pkg/foo/artifactory/repositories.go:773:5: lookup
type AutoMapper func(field reflect.StructField, thing reflect.Value) map[string]interface{}
var lookup = func() func(payload interface{}) map[string]interface{} {
var handlePtr = func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
deref := reflect.Indirect(thing)
if deref.CanAddr() {
if deref.Kind() == reflect.Struct {
return lookup(deref.Interface()) // recursion error 'cyclic definition'
}
return map[string]interface{}{
field.Tag.Get("hcl"): deref.Interface(),
}
}
return map[string]interface{}{}
}
var checkForHcl = func(mapper AutoMapper) AutoMapper {
return func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
if field.Tag.Get("hcl") != "" {
return mapper(field, thing)
}
return map[string]interface{}{}
}
}
lk := map[reflect.Kind]AutoMapper{}
find := func(payload interface{}) map[string]interface{} {
values := map[string]interface{}{}
var t = reflect.TypeOf(payload)
var v = reflect.ValueOf(payload)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
thing := v.Field(i)
for key, value := range lk[thing.Kind()](field, thing) {
values[key] = value
}
}
return values
}
lk[reflect.Struct] = checkForHcl(func(f reflect.StructField, t reflect.Value) map[string]interface{} {
return find(t.Interface())
})
lk[reflect.Slice] = checkForHcl(func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
return map[string]interface{}{
field.Tag.Get("hcl"): thing.Interface().([]string),
}
})
lk[reflect.Ptr] = checkForHcl(handlePtr)
return find
}()
Upvotes: 0
Views: 696
Reputation: 5648
I did as other suggested in various permutations and got the same issue. Ultimately, this is what I got to work
type AutoMapper func(field reflect.StructField, thing reflect.Value) map[string]interface{}
func checkForHcl(mapper AutoMapper) AutoMapper {
return func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
if field.Tag.Get("hcl") != "" {
return mapper(field, thing)
}
return map[string]interface{}{}
}
}
func findInspector(kind reflect.Kind) AutoMapper {
switch kind {
case reflect.Struct:
return checkForHcl(func(f reflect.StructField, t reflect.Value) map[string]interface{} {
return lookup(t.Interface())
})
case reflect.Ptr:
return checkForHcl(func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
deref := reflect.Indirect(thing)
if deref.CanAddr() {
if deref.Kind() == reflect.Struct {
return lookup(deref.Interface())
}
return map[string]interface{}{
field.Tag.Get("hcl"): deref.Interface(),
}
}
return map[string]interface{}{}
})
case reflect.Slice:
return checkForHcl(func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
return map[string]interface{}{
field.Tag.Get("hcl"): castToInterfaceArr(thing.Interface().([]string)),
}
})
}
return checkForHcl(func(field reflect.StructField, thing reflect.Value) map[string]interface{} {
return map[string]interface{}{
field.Tag.Get("hcl"): thing.Interface(),
}
})
}
func lookup(payload interface{}) map[string]interface{} {
values := map[string]interface{}{}
var t = reflect.TypeOf(payload)
var v = reflect.ValueOf(payload)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
thing := v.Field(i)
typeInspector := findInspector(thing.Kind())
for key, value := range typeInspector(field, thing) {
values[key] = value
}
}
return values
}
Upvotes: 0
Reputation: 8395
Your function cannot reference itself by name, because it is an anonymous function. It only becomes named once it gets assigned to the variable lookup
. Lexically, this only happens once the value being assigned is fully parsed. This is different from a normal func
declaration, where the name is available immediately (which makes recursion rather cleaner):
func myFunc(arg) result {
// ... do something ...
// now we can call the function recursively
return myFunc(arg)
}
In your case, a regular func
declaration won't work, so you need some kind of "forward declaration" to make the name available, which requires a small amount of duplication:
// forward declare the function
var myFunc func(arg) result
myFunc = func(arg) result {
// ... do something ...
// now we can call the function recursively
return myFunc(arg)
}
To do the same thing in a global context, see Burak's answer.
Upvotes: 4
Reputation: 51467
The problem is an initialization loop, not recursion. You are referring to a variable from that variable's definition. You can do:
var lookup func() func(payload interface{}) map[string]interface{}
func init() {
lookup=func() func(payload interface{}) map[string]interface{} {...}
}
Upvotes: 1