4 ms·
This has by far the best discussion of the visitor pattern I've yet to come across.
by 12_throw_away 1y ago
This has by far the best discussion of the visitor pattern I've yet to come across.
- tempaway43563 1y agoI went to look for that bit. It said: "Bad" lol
- dgb23 1y agoI don't work in typical OO codebases, so I wasn't aware of what the visitor pattern even is. But there's an _excellent_ book about building an interpreter (and vm) "crafting interpreters". It has a section where it uses the visitor pattern. https://craftinginterpreters.com/representing-code.html#the-visitor-pattern https://craftinginterpreters.com/representing-code.html#the-... I remember reading through it and not understanding why it had to be this complicated and then just used a tagged union instead. Maybe I'm too stupid for OO. But I think that's kind of the point of the grug article as well. Why burden ourselves with indirection and complexity when there's a more straight forward way?
- Jtsummers 1y agoIt's an engineering tradeoff. https://prog2.de/book/sec-java-expr-problem.html https://prog2.de/book/sec-java-expr-problem.html - Not the writeup I was looking for but seems to cover it well. > Why burden ourselves with indirection and complexity when there's a more straight forward way? Because each way has its own tradeoffs that make it more or less difficult to use in particular circumstances. https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt https://homepages.inf.ed.ac.uk/wadler/papers/expression/expr... - Wadler's description of the expression problem.
- dgb23 1y agoThank you for those links. The first one is especially clear. However, this is just not something that I typically perceive as a problem. For example in the book that I mentioned above, I didn't feel the need to use it at all. I just added the fields or the functions that were required. In the first link you provided, the OCaml code seems to use unions as well (I don't know the language). I assume OCaml checks for exhaustive matching, so it seems extremely straight forward to extend this code. On the other hand I have absolutely no issues with a big switch case in a more simple language. I just had a look at the code I wrote quite a while ago and it looks fine.
- recursivedoubts 1y agoI love crafting interpreters and mention it on grugbrain: https://grugbrain.dev/#grug-on-parsing https://grugbrain.dev/#grug-on-parsing but the visitor pattern is nearly always a bad idea IMO: you should just encode the operation in the tree if you control it or create a recursive function that manually dispatches on the argument type if you don't
- zelphirkalt 1y agoAn implementor of a data structure might take precautions for users of the data structure to perform such visiting operations by passing in a visitor-like thing.
- recursivedoubts 1y agoI just don't think it's a significantly better way of dealing w/the problem than a recursive function that dispatches on the arg type (or whatever) using an if statement or pattern matching or whatever. The additional complexity doesn't add significant value IMO. I admit that's a subjective claim.
- zelphirkalt 1y agoI mean, at some point you can also make that recursive function take an argument, that decides what to do depending on the type of the item, to make that recursive function reusable, if one has multiple use-cases ... but that's basically the same as the visitor pattern. There really isn't much to it, other than programming language limitations, that necessitate a special implementation, "making it a thing". Like when Java didn't have lambdas and one needed to make visitors objects, ergo had to write a class for them.
- 12_throw_away 1y agoAs far as I understand it, the limited circumstances when you absolutely need the visitor pattern are when you have type erasure, i.e., can't use a tagged union or its equivalent? In that case visitors are AIUI a very clever trick to use vtables or whatever to get back to your concrete types! but ... clever tricks make grug angry.
- zem 1y agoeven when you have tagged unions, visitors are a useful way to abstract a heterogenous tree traversal from code that processes specific nodes in the tree. e.g. if you have an ast with an `if` node and subnodes `condition`, `if_body`, and `else_body` you could either have the `if node == "if" then call f(subnode) for subnode in [node.condition, node.if_body, node.else_body]` and repeat that for every function `f` that walks the tree, or define a visitor that takes `f` as an argument and keep the knowledge of which subnodes every node has in a single place.
- tayo42 1y agoWhat do you mean by tagged union? And how does it make the visitor pattern not needed?
- PaulHoule 1y agoSee https://en.wikipedia.org/wiki/Tagged_union https://en.wikipedia.org/wiki/Tagged_union In languages influenced by ML (like contemporary Java!) it is common in compiler work in that you might have an AST or similar kind of structure and you end up writing a lot of functions that use pattern matching like switch(node) { type1(a,b) -> whatever(a,b) type2(c) -> process(c) } to implement various "functions" such as rewriting the AST into bytecode, building a symbol table, or something. In some cases you could turn this inside out and put a bunch of methods on a bunch of classes that do various things for each kind of node but if you use pattern matching you can neatly group together all the code that does the same thing to all the different objects rather than forcing that code to be spread out on a bunch of different objects.
- tayo42 1y agoOK yeah I see, that's natural to do with like rust enums Java doesn't support this though I thought?
- PaulHoule 1y agoCurrently Java supports records (finalized in JDK 16) and sealed classes (JDK 17) which together work as algebraic data types; pattern matching in switch was finalized in JDK 21. The syntax is pretty sweet https://blog.scottlogic.com/2025/01/20/algebraic-data-types-with-java.html https://blog.scottlogic.com/2025/01/20/algebraic-data-types-...
- tayo42 1y agooh thats cool. thanks for sharing!
- 9rx 1y ago
- mrkeen 1y agoYou're right. It's an example of changing best practices to fit whatever the language designers released.
- zelphirkalt 1y agoIf you work in a language that can pass closures as arguments, then you don't need a special visitor pattern. It is one of those patterns, that exists because of limitations of languages. Or, if you want to call passing a closure visitor pattern in some cases, it becomes so natural, that it does not deserve special mention as something out of the ordinary. You may be too smart for it.
- ahartmetz 1y agoI care about naming, and I find the name of the visitor pattern infuriatingly bad. Very clubbable. I think I have never created one called "Visitor" in my life. Given the syntax tree example from Wikipedia, I think I'd call it AstWalker, AstItem::dispatch(AstWalker) and AstWalker::process(AstItem) instead of Visitor, AstItem::accept(AstVisitor) and AstVisitor::visit(AstItem). "The walker walks the AST, each items sends it to the next ones, and the walker processes them". That means something. "The visitor visits the AST items, which accept it" means basically nothing. It's more general, but also contains very little useful information. So the visitor might need different names in different situations. Fine. Just add a comment "visitor pattern" for recognizability. I remember a situation where I needed to walk two object trees for a data comparison and import operation. I created an AbstractImporter that walked the two trees in lockstep in a guaranteed order and invoked virtual methods for each difference. It had a non-virtual doImport() for the ordered data walk, and doImport() called virtual methods like importUserAccount(), importUserAccountGrouMemberships() etc. There were two subclasses of AbstractImporter: ImportAnalyzer collected differences to display them, then there was a selection step implemented by a kind of list model + a bit of controller logic, then an ImportWorker to make the selected changes. All rather specific terminology and not exactly the visitor pattern.
- zelphirkalt 1y agoBut the idea of the visitor pattern is not, that it itself walks a tree. The idea is, that it doesn't know how to walk the tree and will be passed in elements from the tree, to its visit method. It does only need to know what to do with one element at a time. The walking is implemented elsewhere.
- ahartmetz 1y agoOne may pretend that it's not the case, but in practice, the visitor/walker does traverse the tree in a particular, systematic order. Walker implies a somewhat systematic traversal (I think), which is what it does. As the implementor of its visit() methods, it doesn't even matter whether the generic Visitor class chooses the path or the nodes do. Visitor is also super vague. Does it just say "Hi" to the whole tree and then leave? Does it only visit the museums and ignore the parks?