Christian Bongiorno
Christian Bongiorno

Reputation: 5648

How do I do this recursion in golang

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

Answers (3)

Christian Bongiorno
Christian Bongiorno

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

Hymns For Disco
Hymns For Disco

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

Burak Serdar
Burak Serdar

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

Related Questions