feat: add visitors-core crate - #108
Conversation
lorisleiva
left a comment
There was a problem hiding this comment.
Thanks for breaking this in smaller PRs! Now we can start talking about the core addition first: What is a node visitor in Rust? Your definition currently seems to be: none haha. You've got a TransformVisitor but we don't have the concept of a generic Visitor<T> like the node/visitor pattern enables.
One of the biggest value in having everything designed as a tree of nodes is you can define a Visitor<T> interface that runs through a tree of Node recursively (or a subset of it) and outputs any type T.
Visitor<Node> is just a special case that goes through the tree and return another tree. Perfect for transformation. Visitor<Node | null> can be pushed further to allow node deletion during the transform. Visitor<string> can be used to display the IDL. Visitor<UsageHistogram> can be used to aggregate some statistics such that another visitor can perform more complex tasks. You get the picture, one of the most valuable feature of the visitor pattern is how rich the API can be.
Now I understand that things may need to be implemented differently in Rust in order to be efficient. A mutable visitor isn't necessarily a bad idea but it should be in addition to the main visitor interface that can return any type T. I also think that being able to construct immutable trees via visitors would be a safer approach if we could implement it that way.
Side note: because Rust has pattern matching, we may not even need visitors to be this huge set of functions (one per node) like it is in TypeScript. Instead, we can just make it a single function that accepts any Node and returns T. It is then within that function that we can define all the different node behaviours using pattern matching.
fair enough, sorry for that. Right now this PR is only the So, we can create 1 generic trait, and use pattern matching to dispatch. As you correctly mentioned that rust doesn't need a method per node like js has. something like follows: pub trait Visitor<T> {
fn visit(&mut self, node: &Node) -> T;
}here we pass:
for examples you provided earlier: impl Visitor<Histogram> for CountKinds {
fn visit(&mut self, n: &Node) -> Histogram {
walk_reduce(self, n, Histogram::one(n.kind()), |a, b| a.merge(b))
}
}
For the transform part (like few words about mutability: the current fold is already immutable - its by-value what do you think about the next 3 steps?:
|
|
Hey @sonicfromnewyoke, sorry for the late reply. I wanted to take my time to think about this properly. I think you've landed on the right core idea: a single generic trait plus pattern matching rather than a per-node method table. Fully on board with: pub trait Visitor<T> {
fn visit(&mut self, node: &Node) -> T;
}The one thing I don't want us to lose is the "default walk" / The real power of the JS visitor is the open recursion: override the node you care about, and let the default walk ( You already had this with // dispatch (JS `visit(node, visitor)`)
pub fn visit<V: Visitor<T> + ?Sized, T>(visitor: &mut V, node: &Node) -> T;
// default walks: the `next`. Behaviour of the generated identityVisitor / mergeVisitor,
// but exposed as functions rather than visitor factories.
pub fn visit_identity<V: Visitor<Node> + ?Sized>(visitor: &mut V, node: &Node) -> Node;
pub fn visit_merge<V: Visitor<T> + ?Sized, T>(visitor: &mut V, node: &Node, /* leaf, merge */) -> T;That way we end up with reusable Here's an example of what it may look like to extend the "identity visitor" with that pattern: impl Visitor<Node> for MyTransformVisitor {
fn visit(&mut self, node: &Node) -> Node {
match node {
Node::Type(RegisteredTypeNode::Array(_)) => { /* custom */ }
other => visit_identity(self, other), // next: recurse via self
}
}
}Regarding the separate typed The main thing a Also worth noting: a transform like So my vote is to land the generic Regarding your 3 steps: looks good! I'd just add the What do you think? |
Problem
codama-rscan build an IDL but has no standard way to walk and rewrite one, so this PR adds the core "visitor" trait that does that.Summary of Changes
codama-visitors-corecrate + describe it in READMETransformVisitortraitinitial part of splitting a large #107 into smaller pieces