fbpx
dirtSimple.orgwhat stands in the way, becomes the way
The Visitor Pattern, Revisited

The Visitor Pattern, Revisited

Sometimes, you need to perform an operation over a data structure that includes many different types of data. But, you don’t want to build the operation into the objects themselves, either because you can’t, or because you don’t want to modify every class every time you have a new kind of operation. For example, you might want to have operations to:

  • “Pretty print” a complex data structure
  • Export the data structure to XML
  • Compile or interpret a data structure (such as an abstract syntax tree)

But, if you’re working with existing classes, and adding a new operation, you don’t necessarily want to have to insert the code into every class. And if someone else wrote the code, you may not even have the option of doing that.

Read more…

Join the discussion
6 comments
  • So what’s the big difference then between doing this with generic functions, as opposed to creating a protocols.Interface, and adapters for all the types/classes?

    Something like:

    class IPrettyPrint(Interface):
    def pretty_print():
    ”’ generic pretty printer ”’

    class PrettyPrintAdapter(object):
    advise(instancesProvide=[IPrettyPrint],
    asAdapterForTypes=[list])
    def __init__(self, list_):
    self.obj = list_
    def pretty_print(self):

  • Syntactic sugar. 🙂

    Also, no adapter instances are created.

    dispatch.on() generates a wrapper function to manage the adaptation process, and the adapter factories just return the adapted object (if applicable) plus the applicable method. So, essentially, it’s syntactic sugar for doing the adaptation, that also avoids the overhead of creating adapter instances when you effectively have a stateless, single-method interface.

    Keep in mind, though, that this is just the case for single-dispatch generic functions. Multi-dispatch and predicate dispatch just aren’t doable with simple adaptation. But, you don’t need that functionality for a simple visitor pattern.

    However, compilers and other complex transformation algorithms can definitely benefit from more advanced dispatch facilities. For example, an optimizing compiler can define rules to match certain variations of a particular source structure, while using a “normal” transformation for subtrees that don’t match.

    In other words, any time that the rules aren’t based on simple type-or-instance conditions, more sophisticated generic functions may be useful in a way that simpler adaptation can’t.

    On the other hand, you can integrate adaptation and generic functions, too. For example, you could use a predicate-dispatch generic function as an adapter function, and then register all the dynamic rules with that generic function.

  • I see, that makes more sense. I just reread all the documentation I could find on it, and things seem much clearer now.

    Isn’t it a worry though that people might start using this to implement static type checking, contrary to the whole idea of Interfaces/protocols?

    Or do you suppose that people who want to do that will just continue to (improperly) use type()/isinstance() calls at the beginning of their functions?

  • I’m not sure what you’re getting at. ‘dispatch.on()’ is implemented with PyProtocols interfaces, so it’s just as flexible “under the hood”. If you don’t like somebody’s implementation of the generic function for some type, just clone it and change it.

    The point is, generic functions are modular and extensible — exactly the opposite of what embedded typechecking results in!

dirtSimple.org

Menu

Stay In Touch

Follow our feeds or subscribe to get new articles by email on these topics:

  • RSS
  • RSS
  • RSS

 

Get Unstuck, FAST

Cover photo of "A Minute To Unlimit You" by PJ Eby
Skip to toolbar