Hacker News new | ask | show | jobs
by vbrandl 2000 days ago
Is there anything that can be achieved by using the visitor pattern [0], that cannot be done by using pattern matching? I have only used the visitor pattern in languages that do not have pattern matching as a language feature (e.g. Java before it got a Scala-like `switch` construct [1]).

Edit: one limitation of pattern matching is, that all values need a common supertype (e.g. be variants of the same enum in Rust, if we see each variant as a type and the enum as the common supertype. There is an RFC [2] to make enum variants accessible as types), while the visitor pattern could be implemented for any set of independent types. On the other hand, you then cannot have a typed collection/container that contains values of these types, so you'd need some common trait like `Visitable` so you could accept an `Vec<dyn Visitable>`.

[0]: https://rust-unofficial.github.io/patterns/patterns/visitor....

[1]: https://openjdk.java.net/jeps/8213076

[2]: https://github.com/rust-lang/rfcs/pull/2593

2 comments

I find that this kind of pattern can be useful if you have a default implementation for each visit_foo method.

For example, suppose that you want to create a traversal that walks through the entire Ast and does something special on just the Name nodes. And another traversal that does something special on just the integer literal nodes. One way to do this is to create a default traversal that walks through the entire tree without doing anything and then create a "subclass" that overrides just the visit_name and another that overrides just the visit_expr method.

One place that I've seen this in the wild is the Ocaml compiler:

* https://github.com/ocaml/ocaml/blob/trunk/parsing/ast_iterat... * https://github.com/ocaml/ocaml/blob/trunk/parsing/ast_mapper...

Or create an iterator and use Iterator::filter, which is probably a bit more idiomatic.
In many languages, creating a pull-based iterator for a tree-shaped data-structure can be tricky. It might require turning the recursion inside out to use an explicit stack... Do you know how well Rust can handle this? I'm not familiar with the details.

In any case, while an iterator might help in the "ast_iterator" example, in the "ast_mapper" example I am not sure if there is a way around it.

The Visitor pattern is great if you want to process a data structure as "stream" without actually instantiating it, in the same way you can read a file line-by-line instead of loading it completely into memory.

For example, serde uses the visitor pattern to encode its intermediate representation. If it used pattern matching instead of the visitor pattern, it would have to instantiate its intermediate representation as an enum, which would add unnecessary overhead.

I guess you are talking about this `Visitor` trait [0]. I have only used serde in combination with the derive macros so please enlighten me, but each `visit_* ` function of the `Visitor` trait already takes a typed value (`bool`, integer types, ...) so these already have too be parsed (instantiated?) from the input string/bytes. Couldn't you have an `SerdeTypes` enum that implements `From` for each `visit_* ` type to remove some boilerplate and then use pattern matching? Could you elaborate where there would be overhead?

Don't get me wrong. I'm sure, the serde developers know better than me and have good reasons to implement it the way they did, but I'd like to understand the rational behind the decision.

Edit: formatting

[0]: https://github.com/serde-rs/serde/blob/master/serde/src/de/m...