Code generator and visitor pattern

J

Jean-Michel Pichavant

Karsten said:
Damn, I mean strongly. At least not for identifying which methods to
call depending on the type/s.

Karsten
Stringly is the perfect combination of strictly and strongly. Nice one :)

JM
 
M

Mick Krippendorf

Karsten said:
The visitor pattern uses single-dispatch, that is, it determines
which method to call be the type of object passed in.

Say, in Python, I have an object o and want to call one of it's methods,
say m. Then which of possibly many methods m to call is determined by
the type of o, and nothing else (at least without further magic) -
hence, it is single dispatch. The VP uses two single dispatch calls (one
being a callback) to accomplish double dispatching.

Java has a form of multiple dispatch through function overloading. In
fact, it's still single dispatch, because which of the targets
overloaded methods gets called is determined at compile time by the
staticly known types of the methods parameters, AKA the formal parameter
types.

Anyway. Although VP *uses* double dispatch, it does not necessarily rely
on function overloading. In Java the VP is most often implemented like this:

<java>

interface IASTNode {
void accept(IASTVisitor);
}

class IfNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visit(this);
}
}

class ElseNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visit(this);
}
}

interface IASTVisitor {
void visit(IfNode node);
void visit(ElseNode node);
...
}
class PrettyPrinter implements IASTVisitor {
public void visit(IfNode n) {
...
}
public void visit(ElseNode n) {
...
}
}

</java>

but it could as well be implemented like this:

<java>

interface IASTNode {
void accept(IASTVisitor);
}

class IfNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visitIfNode(this);
}
}

class ElseNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visitElseNode(this);
}
}

interface IASTVisitor {
void visitIfNode(IfNode node);
void visitElseNode(ElseNode node);
...
}
class PrettyPrinter implements IASTVisitor {
public void visitIfNode(IfNode n) {
...
}
public void visitElseNode(ElseNode n) {
...
}
}

</java>

If Java were *really* a multiple dispatch language, it wouldn't be
necessary to repeat the accept-code for every subclass. Instead a single
accept method in the base class would suffice. In fact, with true
multiple dispatch VP wouldn't even be needed.


Regards,
Mick.
 
M

Mick Krippendorf

Hello,

[os.path.walk vs os.walk]
There is a big conceptual difference between os.path.walk and os.walk.
The first works like a framework: you pass a function to it and
os.path.walk is in charging of calling it when needed. The second works
like a library: os.walk flattens the hierarchical structure and then
you are in charge of doing everything you wish with it.

os.walk is the Pythonic way, and you suggested to follow that
approach; for instance elementTree and lxml (libraries for parsing XML
data) work exactly that way. Actually one of the motivating examples for
the introduction of generators in Python was their use in flattening
data structure, i.e. exactly the pattern used by os.walk.

The Visitor Pattern isn't about traversing, so they could as well have
had an os.walk() that took a visitor object. Instead, it's about the
untangling of unrelated stuff. Not the traversing vs. everything else -
that's what iterators are for, like you said. But if you want to be able
to add new types of operations without ever touching the code of the
objects on which to apply those operations, then the VP is an easy way
to accomplish things:

<python>

class IfNode:
def apply(self, operation):
operation.handleIfNode(self)
....

class ElseNode:
def apply(self, operation):
operation.handleElseNode(self)
....

class PrettyPrinter:
def handleIfNode(self, if_node):
# print if_node pretty
def handleElseNode(self, else_node):
# print else_node pretty
....

class Interpreter:
def handleIfNode(self, if_node):
# interpret if_node
def handleElseNode(self, else_node):
# interpret else_node
....

class AST:
def apply(self, operation):
# apply operation to all nodes
....

some_ast = ...
some_ast.apply(PrettyPrinter())
some_ast.apply(Interpreter())

</python>

The traversing in AST.apply() is not really part of the pattern, it
could also be done in the client code. The VP lives in the relation
between the ...Node and the operation classes. It Encapsulates What
Varies and helps to uphold the Open/Closed Principle, because to add new
operations one does not need to touch the ...Node classes. It implements
double dispatching in a single dispatch language.


Regards,
Mick.
 
M

Mark Lawrence

Say, in Python, I have an object o and want to call one of it's methods,
say m. Then which of possibly many methods m to call is determined by
the type of o, and nothing else (at least without further magic) -
hence, it is single dispatch. The VP uses two single dispatch calls (one
being a callback) to accomplish double dispatching.

Java has a form of multiple dispatch through function overloading. In
fact, it's still single dispatch, because which of the targets
overloaded methods gets called is determined at compile time by the
staticly known types of the methods parameters, AKA the formal parameter
types.

Anyway. Although VP *uses* double dispatch, it does not necessarily rely
on function overloading. In Java the VP is most often implemented like this:

<java>

interface IASTNode {
void accept(IASTVisitor);
}

class IfNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visit(this);
}
}

class ElseNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visit(this);
}
}

interface IASTVisitor {
void visit(IfNode node);
void visit(ElseNode node);
...
}
class PrettyPrinter implements IASTVisitor {
public void visit(IfNode n) {
...
}
public void visit(ElseNode n) {
...
}
}

</java>

but it could as well be implemented like this:

<java>

interface IASTNode {
void accept(IASTVisitor);
}

class IfNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visitIfNode(this);
}
}

class ElseNode implements IASTNode {
void accept(IASTVisitor visitor) {
visitor.visitElseNode(this);
}
}

interface IASTVisitor {
void visitIfNode(IfNode node);
void visitElseNode(ElseNode node);
...
}
class PrettyPrinter implements IASTVisitor {
public void visitIfNode(IfNode n) {
...
}
public void visitElseNode(ElseNode n) {
...
}
}

</java>

If Java were *really* a multiple dispatch language, it wouldn't be
necessary to repeat the accept-code for every subclass. Instead a single
accept method in the base class would suffice. In fact, with true
multiple dispatch VP wouldn't even be needed.


Regards,
Mick.

Boilerplate, boilerplate everywhere, but not a beer to drink.

Hope everyone at EuroPython is having a good time.

Kindest regards.

Mark Lawrence.
 
M

Mick Krippendorf

Mark said:
Boilerplate, boilerplate everywhere, but not a beer to drink.

That's Java for ya.

<python>

class ASTNode:
def accept(self, visitor):
getattr(visitor, self.__class__.__name__)(self)

class IfNode(ASTNode):
... # inherits generic accept method

class ElseNode(ASTNode):
... # inherits generic accept method

class PrettyPrinter:
def IfNode(self, node):
...
def ElseNode(self, node):
...

</python>

Regards,
Mick.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,175
Messages
2,570,942
Members
47,476
Latest member
blackwatermelon

Latest Threads

Top