The traversal is not really part of the visitor pattern. The element.accept(visitor) function together with the visitor.visitElementType(element) are the identifying part of the visitor pattern, and they completely disappear with CLOS.
A classic example is different parsers for the same set of expression types. The expressions likely form a tree, you may not need a list of expressions at all, so no mapcar.
The motivating scenario for the Visitor pattern is processing an AST that has polymorphic nodes, to achieve different kinds of processing based on the visiting object, where special cases in that processing are based on the AST node kind.
Even if we have multiple dispatch, the methods we have to write for all the combinations do not disappear.
Additionally, there may actually be a method analogous to accept which performs the recursion.
Suppose that the AST node is so abstract that only it knows where/what its children are. Then you have some:
(We might want recurse-bottom-up and recurse-top-down.)If all the AST classes derive from a base that uniformly maintains a list of n children, then this would just be in the base: (for-each-child ch (recurse ch fun)) or whatever.
Suppose we don't want to use a function, but an object (and not to use that object as funcallable). then we need (let's integrate the base class idea also):
Now we have a myriad method specializations of do-action. By doing it this way, we get rid of a lambda shim. Instead of: we can just have: It's only not the Visitor Pattern because I used recurse instead of accept, do-action instead of visit, and agent instead of visitor.