-
Notifications
You must be signed in to change notification settings - Fork 44
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
Add a TransformPropertyValueDirectional property value walker #2760
Add a TransformPropertyValueDirectional property value walker #2760
Conversation
This change is part of the following stack: Change managed by git-spice. |
2b42a74
to
a2644af
Compare
bc6bef7
to
7da1aec
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2760 +/- ##
=======================================
Coverage 68.60% 68.61%
=======================================
Files 322 322
Lines 41223 41238 +15
=======================================
+ Hits 28281 28294 +13
- Misses 11347 11351 +4
+ Partials 1595 1593 -2 ☔ View full report in Codecov by Sentry. |
a2644af
to
fd71cbe
Compare
7da1aec
to
dbcd3ad
Compare
fd71cbe
to
16dbc86
Compare
dbcd3ad
to
12baad1
Compare
16dbc86
to
83dba22
Compare
12baad1
to
0d4b52a
Compare
83dba22
to
282ac96
Compare
0d4b52a
to
220bad9
Compare
282ac96
to
34568a1
Compare
220bad9
to
9ed2faa
Compare
// TransformPropertyValueLimitDescent is a variant of TransformPropertyValue that allows the transformer | ||
// to return a LimitDescentError to indicate that the recursion should not descend into the value without | ||
// aborting the whole transformation. | ||
func TransformPropertyValueLimitDescent( | ||
path resource.PropertyPath, | ||
transformer func(resource.PropertyPath, resource.PropertyValue) (resource.PropertyValue, error), | ||
value resource.PropertyValue, | ||
) (resource.PropertyValue, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function conflates a bunch of stuff. Traversal order is not related to supporting a LimitDescentError
. The idiomatic Go API for tree walking looks like this:
var SkipChildren = skipChildren{}
type Transformer = func(
resource.PropertyPath, resource.PropertyValue, entering bool,
) (resource.PropertyValue, error)
func TransformPropertyValue(
path resource.PropertyPath,
transformer Transformer,
value resource.PropertyValue,
) (resource.PropertyValue, error)
We can then use TransformPropertyValue
to construct all the functions we need:
func TransformPropertyValuePreorder(
path resource.PropertyPath,
transformer func(
resource.PropertyPath, resource.PropertyValue,
) (resource.PropertyValue, error),
value resource.PropertyValue,
) (resource.PropertyValue, error) {
return TransformPropertyValue(path, func(
path resource.PropertyPath, value resource.PropertyValue, entering bool,
) (resource.PropertyValue, error) {
if entering { return transformer(path, value) }
return value, nil
}, value)
}
Can we keep to a general function to handle the traversal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Traversal order is not related to supporting a LimitDescentError
Not sure I understand what you mean here - if you visit children before their parents then a visitor doesn't get a chance to return a LimitDescentError
.
The API you suggested is cleaner and I have indeed seen the pattern with entering
elsewhere, so happy to refactor it like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored the module and kept just one traversal function. LMK what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I recall seeing entering
somewhere but I can't recall where. This latest version is OK-ish to reason about. The boolean needs to be called entering
otherwise the API causes boolean blindness.
Do you have refs to Go libs where you've seen this?
I'd suspect idiomatic Go would be further reliant on mutation :) But there's e.g. https://github.com/hashicorp/go-cty/blob/v1.4.1-0.20200414143053-d3edf31b6320/cty/walk.go#L87 that does not mutate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just found https://github.com/fogfish/golem for light weekend reading, this is going to be fun but we probably don't want to go too far down that route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/fogfish/golem this looks pretty fun indeed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err := ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { |
The Markdown parsing library we use uses the entering
pattern for its recursion.
e365498
to
1b16b10
Compare
1b16b10
to
689dea2
Compare
34568a1
to
b4c0769
Compare
70fb526
to
d32594b
Compare
This is generally a bad idea I think. Once you have variations on the recursion pattern they get difficult to explain, and it is much easier to maintain an actually direct recursive function than to share a recursion pattern. And when debugging you need to step through the concrete function and the general recursion function that is not very intuitive, this is not great. I'd inline this. If inlining introduces too much repetitive code into the call site, I'd try one pattern I have seen work well that looks like this. In the library you have an API that looks like this:
This API lets you see the children of a PropertyValue, and then substitute them and get a similar-ish PropertyValue with different children. Note that this is a functional API (I'm borrowing from something I've seen in F# and Haskell), perhaps an imperative walker can be simpler but to be safe it'd need a deep clone function as well. Continuing with this, if you have a complex transformation that's recursive, you write it like this:
If you want to modify the recursion pattern like walking up or down or special casing, you then work within |
https://dl.acm.org/doi/10.1145/604174.604179 is a reference here. Realized in https://hackage.haskell.org/package/uniplate for example. |
d32594b
to
5627094
Compare
} | ||
|
||
type TransformerDirectional = func( | ||
resource.PropertyPath, resource.PropertyValue, bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name the fields here.
The latest version with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
) | ||
} | ||
|
||
type SkipChildrenError struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a comment explaining what it does.
// TransformPropertyValueDirectional is a variant of TransformPropertyValue that allows the transformer | ||
// to visit nodes both on the way down and on the way up. | ||
// | ||
// The transformer can return a SkipChildrenError to indicate that the recursion should not descend into the value. | ||
func TransformPropertyValueDirectional( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather this be called TransformPropertyValue
, but we can change that in a subsequent PR.
This PR adds a new property value walker function to the
propertyvalue
module:TransformPropertyValueDirectional
.This is different to
TransformPropertyValue
in two ways:transform
function can return aSkipChildrenError
to prevent the recursion from walking the children of the value currently being visited.This is necessary for #2761 to handle set value comparisons, as we need to be able to recursively walk a property value without recursing into nested sets (as nested sets can be reordered during planning).