I do think visitor pattern, type classes and pattern matching have different areas where responsibility lies and how it is distributed (with pattern matching the most local, visitor pattern extensible without by classes and type classes - which I prefer - extensible at the call site).
I think the Visitor pattern is an antipattern. I've never seen a non-trivial application that didn't become a huge mess. It fundamentally violates the basic OO principle of encapsulating state and behavior. Anything else -- reifying the events into first-class types, factoring out the overlap into a new type, generics, even Scala-style traits -- is better.
The whole point is to separate behavior from the datastructure. Yes, this doesn't fit nicely into OO principles, but strictly that's true any time you externally query an object for state and make a conditional decision based on the result.
Inevitably, any method (visitor, pattern matching, if elsif) of customizable walking of a tree datastructure separates behavior from the tree object. That's the point, though, again.
Now, you can say wanting to do that (customizable tree walking) in itself is an antipattern and shouldn't be done. That just doesn't match well with reality.
I've seen many cases where the visitor pattern is about the best one can do. Yes, it becomes ugly but that's usually due to the complexity of the attempted tree walking, e.g. for transformation.
A lot of the alternative approaches in the functional world become ugly too once there's a need to offer maximum customizability, e.g. in terms of order of evaluation.
If you are able to sufficiently constrain the domain you can get away with some simplied templates, but where I see visitors most often is in API's wanting to offer any kind of tree walking.
We absolutely use a variant of this technique in Haskell. We absolutely do not need type classes (of any order) to make it work (although type classes and type families certainly clean up the implication). We need a Fixpoint lifting, a way to consider an object as a functor (which we could do at the value level). The rest is simple functions.
I was surprised how similar it looks in Java. I hadn't considered how it would look.
After 10years of Scala development, SBT is still the largest pain. Compile speed, picking up changes takes many seconds, arcane syntax, magic error messages, fragile configuration files.
Will try mill, I wonder why no other build tools for scala showed up, with SBT being such a pain for many people.
We use pants internally. (https://pantsbuild.org) Comes from Twitter, and - if I understand correctly - based on many of the ideas from Bazel. It’s actively maintained and updated.
I do think visitor pattern, type classes and pattern matching have different areas where responsibility lies and how it is distributed (with pattern matching the most local, visitor pattern extensible without by classes and type classes - which I prefer - extensible at the call site).