I assume there is already a proposal out?
// Client visitor code
match(
thingy,
[](const PurpleThingy& ) {std::cout << "purple thingy\n";},
[](const LittleThingy& ) {std::cout << "little thingy\n";},
[](const auto& ) {std::cout << "any other type\n";}
);
Working example:
https://coliru.stacked-crooked.com/a/dfed39bc7fcdfb01https://coliru.stacked-crooked.com/a/be5c44281eea8bc4
Then only unfortunate missing piece of the puzzle is that there's no trivial way to create a closure out of this, so it requires a bit more manual work to propagate local state to the visitor.
http://open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1371r0....
And so we end up with a huge pile of visitor code, and there is one or maybe two actual visitors.
When it would have been far easier to read and modify if it was just coded out normally.
Nevertheless, for many problems, like compiler construction, I’ve never seen a pattern that matched the utility of the visitor (even outside of imperative and object-oriented patterns)
I don't know if I have ever actually seen a true implementation of this pattern. What I have seen is a pattern someone called a Visitor where a tree is iterated over and the "Visitor" makes virtual function calls on the composite node in a single function body without ever doing something different depending on the type of the node.
This example actually makes clear why you'd need to do it this was in C++ at least. Not sure which other languages would need this. It's certainly not pretty. Then again, dispatching twice is not so bad compared to your average Java-style over-engineering.
[0] https://eli.thegreenplace.net/2016/the-expression-problem-an...
The reason to use this pattern is to leverage real polymorphism. In Squeak/Pharo, the graphics system is called Morphic, which uses this pattern all the time to draw different kinds of objects to different kinds of Canvases. With a visitor pattern, each object can "know" how to draw itself to a given canvas.
This kind of abomination even has a name.
interactor_p->interact(thingy_p)
Would this still be considered the visitor pattern or is the extra layer of indirection important? "Square class"
drawOn: aCanvas
aCanvas line: (some point) to: (some point).
"etc"
"Triangle class"
drawOn: aCanvas
aCanvas line: self leftVertex to: self rightVertex.
"etc"
"Canvas class"
drawShapes: aCollection
aCollection do: [ :shape |
shape drawOn: self ]https://en.wikipedia.org/wiki/Open–closed_principle
Otherwise you find that your AST data structure is never finished -- you are constantly added stuff to it. I wish there was a clearer way in C++, but this is the C++ solution.
Someone else up-thread mentioned what I think is the key requirement making visitors worthwhile is non-trivial tree traversal. ASTs seem to fit this description more than any other data structure I've had to work with day-to-day.
Outside of language trees, I wonder where else visitors are common?
So that is to say, we can still have a situation where we have a tree of objects T with nodes N of different classes (expression, if-statement, ...) and visitor objects of different classses (pretty-printer, evaluator, ...).
Then given some generic function G and visitor object V, we walk the nodes of the tree, and simply (funcall G N V) for each node N that we encounter.
The remaining verbiage is then in all the method specializations we have to write for G for all the N V combinations that occur.
For all the framework brevity, we yet have an additional flexibility relative to the Visitor Pattern: namely, we can not only vary the choice of visitor object V, but also of G. The Visitor Pattern fixes the generic function in terms of some hard-coded some visit()/accept() protocol.
I clicked the link, hoping to disable x,y,z and there is just text. No options!
Not reading further.
Which implements the pseudocode: https://cppcrypt.tumblr.com/post/168134402897
main {
thingies = [ purpleThingy, littleThingy ]
interactions = [ commentOn, cherish ]
for (interaction in interactions)
for (thing in thingies)
interaction.interact(thing)
}
The author does mention things like "function pointers can be used in simple cases" and "std::variant would avoid the need to overload the method". But the main reason for having the C++ code the way it is is because "you can't dispatch to overloaded methods at runtime".
https://cppcrypt.tumblr.com/post/169439207562 ff.I'd probably just do type checks and maybe use a 2D table for the possible interactions.
Would love to see more stories.
The even deeper problem is what ASTs are meant to represent, to which the answer would be "possibly a lot of diverse things". Diversity is never good in a computational context. But I don't see a good way to avoid it in the context of programming languages and ASTs. The reason for the diversity is that programming languages should allow humans to specify what should happen in very few keystrokes.