-
Notifications
You must be signed in to change notification settings - Fork 283
Implement rmodify()
#946
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
Implement rmodify()
#946
Conversation
R/modify-recursive.R
Outdated
#' @param x A list. | ||
#' @param f_leaf A function applied to each leaf. | ||
#' @param f_pre A function applied to each node or leaf, before leaves are | ||
#' transformed. |
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.
It's not immediately clear what transformation is meant by "transformed, nor the relationship to the tree traversal.
I would clarify this statement further along the lines ".. applied to each node when the tree is traversed down, aka before the leaves are transformed with f_leaf
.
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 actually made me thinking, is it necessary to apply f_pre
and f_post
to leafs? I think most commonly the f_pre
and f_post
will be used on nodes and in general the same operation is unlikely to be applied on both leafs and nodes. As a result a user would need to add an extra check for leafs inside f_pre
and f_post
.
Also applying all 3 transformations on leafs is redundant and slightly confusing.
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’m glad you bought that up because that’s what I was thinking too 😀
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.
Hmmmm, what happens if f_pre
changes x
from a node to a leaf?
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.
As a map
-like function, I think it should preserve the types/shape of the data structure; f_pre
/f_post
should have Node -> Node
type and should not delete a node or transmute a node into a leaf. Otherwise, the behavior is a bit surprising, depending on how the tree is traversed.
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 think being able to change a type of a node is a very important feature. For example one might want to change the end list nodes (prior to leafs) into a proper atomic vector.
I find it hard to think of good use-cases for f_pre, but the one you mentioned seem an important one. It's quite possible that the purpose of an operation is to transform a sub-tree into a leaf. The traversal stops right there. This can be important for deeply nested trees where performance starts playing a role. On the other hand p_leaf
can capture this use case as well.
So there are 4 options:
- leave as it is and add a note that
f_pre
should return a node, otherwise the behavior is undefined. - add an extra check for leaf after f_pre
- make f_pre/f_post asymetric and apply f_pre to the leaf node but not f_post
- remove f_leaf and add extra parameter to
f_post/f_pre
,leaf=TRUE
I am somewhat indifferent between 1 and 2. Though 2 seems a bit cleaner conceptually and the default penalty of is.list
is trivial.
R/modify-recursive.R
Outdated
if (!is_vector(x)) { | ||
cli::cli_abort("{.arg x} must be a vector, not {.obj_type_friendly {x}}.") | ||
} |
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 think it'd be more consistent if we checked for a leaf and return leaf(x)
in that case.
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.
Why don't we require x
to be a list with vec_check_list()
?
I feel like allowing non-lists isn't useful because pre/post won't ever be applied? I guess that depends on what p_leaf()
returns though (i.e. in theory it could say a numeric value is a node, but i feel like that isn't useful)
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 think there's probably some invariant around modify_tree(x, f_pre = \(y) modify_tree(y, f_leaf)) == modify_tree(x, f_leaf)
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.
My preference would be to allow for as wide class of objects as possible. @lionel-'s point about allowing rmodify
on all objects that [[<-
is allowed on makes sense to me.
Use cases:
- Side effectful recursive modification of nested environments
- Recursive processing of language objects. For instance, if one wants to modify specific parts of a function.
R/modify-recursive.R
Outdated
if (!is_vector(x)) { | ||
cli::cli_abort("{.arg x} must be a vector, not {.obj_type_friendly {x}}.") | ||
} |
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.
Why don't we require x
to be a list with vec_check_list()
?
I feel like allowing non-lists isn't useful because pre/post won't ever be applied? I guess that depends on what p_leaf()
returns though (i.e. in theory it could say a numeric value is a node, but i feel like that isn't useful)
rmodify(list(1, list(2, list(3))), f_post = list_flatten), | ||
list(1, 2, 3) |
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.
Neat, it's like list_squash()
Fixes #720
@vspinu here's a concrete proposal for
rmodify()
that I'd love to get your feedback on.To do: