Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bounded recursion example breaks on cue 0.6 #80

Open
iyefrat opened this issue Mar 12, 2024 · 2 comments
Open

Bounded recursion example breaks on cue 0.6 #80

iyefrat opened this issue Mar 12, 2024 · 2 comments

Comments

@iyefrat
Copy link

iyefrat commented Mar 12, 2024

The bounded recursion function works for cue 0.5, but breaks on cue 0.6. To reproduce.

To reproduce the issue, take the following cue code (copied from the website, presented here as one block for easy of reproducibility):

package r

import "list"

#RecurseN: {
	// this is the bound on our recursion
	#maxiter: uint | *4

	// This is the function list element
	// we generate this to simulate recursion
	#funcFactory: {
		#next: _
		#func: _
	}

	// this is our "recursion unrolling"
	for k, v in list.Range(0, #maxiter, 1) {
		// this is where we build up our indexed functions and the references between them
		#funcs: "\(k)": (#funcFactory & {#next: #funcs["\(k+1)"]}).#func
	}

	// our final value needs to be null
	#funcs: "\(#maxiter)": null

	// we embed the head of the list so that the values
	// we write this into can be used like other CUE "functions"
	#funcs["0"]
}

#DepthF: {
	#next: _
	#func: {
		#in:    _
		#basic: int | number | string | bytes | null
		out: {
			// if we have a basic type, we are at a leaf, depth is 1
			if (#in & #basic) != _|_ {1}

			// if we are not a basic type, then we are 1 + the max of children
			if (#in & #basic) == _|_ {
				// this is our "recursion" for each child
				let depths = [ for k, v in #in {(#next & {#in: v}).out}]
				list.Max(depths) + 1
			}
		}
	}
}

#Depth: #RecurseN & {#maxiter: 11, #funcFactory: #DepthF}

tree: {
	a: {
		foo: "bar"
		a: b: c: "d"
	}
	cow: "moo"
}

d: #Depth & {#in: tree}

and run (with recur.cue containing the above):

$ cue export --out json recur.cue

with cue 0.5, the result will be:

{
    "tree": {
        "a": {
            "foo": "bar",
            "a": {
                "b": {
                    "c": "d"
                }
            }
        },
        "cow": "moo"
    },
    "d": {
        "out": 5
    }
}

with cue 0.6 (or 0.7.1), the result errors:

d: cannot add field #maxiter: was already used:
    ./recur.cue:49:22

if I remove the #maxiter field, I get a result, but out is now missing:

{
    "tree": {
        "a": {
            "foo": "bar",
            "a": {
                "b": {
                    "c": "d"
                }
            }
        },
        "cow": "moo"
    },
    "d": {}
}

Not sure why any of this happens to be honest, haven't been able to find this issue or a lead anywhere.

@verdverm
Copy link
Member

Bounded recursion is not really something you should be using anyway. It's more of a "cool that you can do this" but not good or idiomatic CUE

Does it work with cue eval or --out cue?

Looking at the error, and knowing there have been upstream changes to references, it might be worth asking the CUE team if this should still work as well. Or maybe it's an issue with when defaults get evaluated?

My gut says this should still work

@verdverm
Copy link
Member

cue eval tmp.cue  
#RecurseN: {
    _
    #maxiter: 4
    #funcFactory: {
        #next: _
        #func: _
    }
    #funcs: {
        "4": null
        "0": _
        "1": _
        "2": _
        "3": _
    }
}
#DepthF: {
    #next: _
    #func: {
        #in:    _
        #basic: int | number | string | bytes | null
        out:    1
    }
}
#Depth: {
    #maxiter: 11
    #in:      _
    #basic:   int | number | string | bytes | null
    #funcFactory: {
        #next: _
        #func: {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
    }
    #funcs: {
        "11": null
        "0": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "1": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "2": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "3": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "4": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "5": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "6": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "7": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "8": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "9": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
        "10": {
            #in:    _
            #basic: int | number | string | bytes | null
            out:    1
        }
    }
    out: 1
}
tree: {
    a: {
        foo: "bar"
        a: {
            b: {
                c: "d"
            }
        }
    }
    cow: "moo"
}
d: #Depth & {
    #in: tree
}

This works a bit more, but not necessarily the out values I expect either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants