I've always seen the visitor pattern as a poor-mans pattern matching. How do you solve the same thing with callbacks?
Instead of defining a Visitor interface and then making objects to implement the interface and then passing those objects to whatever traverses a graph or iterates through something, you pass the function, that will be called, by whatever traverses a graph or iterates through something.
Kind of like how sum types and matching are implemented in library code? Example from D here:
Fahrenheit toFahrenheit(Temperature t)
{
return Fahrenheit(
t.match!(
(Fahrenheit f) => f.degrees,
(Celsius c) => c.degrees * 9.0/5 + 32,
(Kelvin k) => k.degrees * 9.0/5 - 459.4
)
);
}
The magical "visitor" pattern becomes nothing more than simple callback passing or passing a function, which is one of the most natural things to do in a modern programming language.
State machines at least are useful as a conceptual thing, to find a way to think about how one solves a problem, no matter how they are ultimately implemented in the end.