Most active commenters
  • tayo42(3)
  • zelphirkalt(3)

←back to thread

1087 points smartmic | 17 comments | | HN request time: 1.249s | source | bottom
Show context
12_throw_away ◴[] No.44303909[source]
This has by far the best discussion of the visitor pattern I've yet to come across.
replies(3): >>44304154 #>>44304309 #>>44304662 #
1. dgb23 ◴[] No.44304309[source]
I 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-...

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?

replies(6): >>44304428 #>>44304648 #>>44304698 #>>44304986 #>>44306960 #>>44316589 #
2. Jtsummers ◴[] No.44304428[source]
It's an engineering tradeoff.

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/expr... - Wadler's description of the expression problem.

replies(1): >>44304739 #
3. recursivedoubts ◴[] No.44304648[source]
I love crafting interpreters and mention it on grugbrain:

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

replies(1): >>44316626 #
4. 12_throw_away ◴[] No.44304698[source]
As 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.
replies(1): >>44305249 #
5. dgb23 ◴[] No.44304739[source]
Thank 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.

6. tayo42 ◴[] No.44304986[source]
What do you mean by tagged union? And how does it make the visitor pattern not needed?
replies(1): >>44305481 #
7. zem ◴[] No.44305249[source]
even 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.
8. PaulHoule ◴[] No.44305481[source]
See 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.
replies(1): >>44305855 #
9. tayo42 ◴[] No.44305855{3}[source]
OK yeah I see, that's natural to do with like rust enums

Java doesn't support this though I thought?

replies(2): >>44305973 #>>44310689 #
10. PaulHoule ◴[] No.44305973{4}[source]
Currently 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-...

replies(1): >>44306190 #
11. tayo42 ◴[] No.44306190{5}[source]
oh thats cool. thanks for sharing!
12. mrkeen ◴[] No.44306960[source]
You're right. It's an example of changing best practices to fit whatever the language designers released.
13. 9rx ◴[] No.44310689{4}[source]
> that's natural to do with like rust enums

Stands to reason. Rust "enums" are tagged unions (a.k.a. sum types, discriminated unions).

In implementation, the tag, unless otherwise specified, is produced by an enum, which I guess is why it got that somewhat confusing keyword.

14. zelphirkalt ◴[] No.44316589[source]
If 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.
15. zelphirkalt ◴[] No.44316626[source]
An 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.
replies(1): >>44319782 #
16. recursivedoubts ◴[] No.44319782{3}[source]
I 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.

replies(1): >>44320748 #
17. zelphirkalt ◴[] No.44320748{4}[source]
I 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.