This is the expected behaviour. You have only partially defined the dictionary (at least one value is undefined, because you have explicitly set it to an undefined value) so it is treated as undefined
If, as you’ve stated, you want foo to be equal to {}, you should just do foo: {}.
Perhaps this or evaluating something using set_fact to reassign the actual value will get you out of most situations, but it feels tedious. But I suppose that’s more about how to elegantly work around the (to me unexpected) properties of lazy evaluation, which is for another topic. Thanks.
This isn’t an issue with lazy variable evaluation, you just cannot refer to a variable to define itself. Use the same variable name to fully override variables defined with lesser precedence, otherwise you should use intermediate var(s).
I suppose that’s right, and I was mistaking expressions like (JavaScript)
foo = foo; // works
which works just fine if foo has been defined, for definitions like (again JavaScript):
let foo = foo; // does not work
which will not work.
What I’m after is a way to define a var called foo in terms of whatever foo happens to be in the surrounding scope, which can happen if some role I want to use happens to have a parameter with a name that is already used. Intermediate facts are the most reliable way to do this I suppose.
But if not for lazy evaluation, there seems to be no reason to require the mention of foo in foo: "{{ foo }}" to refer to the variable being defined, as opposed to the value it has at that point. Although this makes me wonder why JavaScript doesn’t work that way.
I think that is a common misconception of the way templating happens. Variable precedence happens before templating, and the variable precedence maintains copies of the var at all different levels it is defined, and precedence does not preserve any context about lower levels of precedence.
So defining foo: "{{ foo }}" at any level, is a self referential assignment, not referencing lower precedence levels.
Effectively we just go through the precedence hierarchy, and merge keys over top of each other, then we pass that on to templating after the merging.
Thanks for clarifying. Sounds like something that may be hard to change, though it seems it would be worthwhile otherwise. I’d love to be able to treat roles like functions, and this is one of the ways the analogy breaks; defining role vars is not like passing arguments to a function in an important way.
I did find a way to wrap include_role in a plugin that eagerly evaluates its parameters and assigns the one called input to include_role’s vars, which I believe gets me what I want. I have some reservations about the particular way I went about wrapping include_role, but I guess the more important question is what I’m giving up by using eager evaluation. I’m thinking the trade-off might be a simpler mental model for less static analysis and more overhead. I suppose I’m stretching this topic a little, but any comments on this would be much appreciated.