Skip to content

Instantly share code, notes, and snippets.

@nuttycom
Last active January 8, 2017 21:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nuttycom/6e286855bc67b3d89096824eba33d531 to your computer and use it in GitHub Desktop.
Save nuttycom/6e286855bc67b3d89096824eba33d531 to your computer and use it in GitHub Desktop.
interface ShapeVisitor<A>
{
// The implementation of ApplyRect defines handling for the
// operation defined by this visitor in the case that the
// shape is a rectangle.
public A ApplyRect(IRectangle rect);
// The implementation of ApplyCircle defines handling for the
// operation defined by this visitor in the case that the
// shape is a circle.
public A ApplyCircle(ICircle circle);
}
public class AreaVisitor : ShapeVisitor<double>
{
public double ApplyRect(IRectangle rect)
{
return rect.height * rect.width;
}
public double ApplyCircle(ICircle circle)
{
return Math.PI * circle.radius * circle.radius ;
}
}
public class PerimeterVisitor: ShapeVisitor<double>
{
public double ApplyRect(IRectangle rect)
{
return 2 * (rect.height + rect.width);
}
public double ApplyCircle(ICircle circle)
{
return Math.PI * circle.radius * 2;
}
}
abstract class IShape {
public A accept<A>(ShapeVisitor<A> visitor);
// This becomes essentially syntactic sugar; it need not be defined as a method on the interface.
public double GetArea()
{
return accept(new AreaVisitor());
}
// I will not define GetPerimeter here - there is no purpose. The user
// can simply call myShape.accept(new PerimeterVisitor()) if they
// wish that functionality. The point here is that the end user can "extend"
// the interface by adding new Visitor implementations - they do not need
// IShape to be altered.
}
class ICircle : IShape {
public A accept<A>(ShapeVisitor<A> visitor) {
return visitor.ApplyCircle(this);
}
}
class IRectangle: IShape {
public A accept<A>(ShapeVisitor<A> visitor) {
return visitor.ApplyRect(this);
}
}
@nuttycom
Copy link
Author

nuttycom commented Jan 4, 2017

The whole point of using a type system is to exclude invalid states from the state space of your application by construction. In languages like C#, we have built-in product types (read: objects) which readily multiply the size of our state space, but lack built-in sum types to help us reduce it. This is a grave error; Visitor helps us to mitigate it. For additional context, here's another talk that I gave a few years ago on the subject: https://github.com/nuttycom/lambdaconf-2014/blob/gh-pages/slides.md

@giuliohome
Copy link

giuliohome commented Jan 4, 2017

"... the appropriate thing to do is to factor that implementation out to a function of two arguments" Well, I was waiting for this answer. That function of two arguments is a backdoor where the benefits of the visitor can be possibly lost. Because it is a function of two doubles and now it can be wrongly replaced by another function with the same signature (let me fantasize that we want a common function of r1,r2 for circle and ellipse, degenerating in the circle's area for r1 = r2).
Now, if you compare this logical error (erroneously switching the common underlying function) - that will silently give a wrong result in production - to the runtime exception due to a wrong type conversion (I'd say that an explicit cast is not even needed), maybe the latter could be better in term of ensuring the consistency of a "valid" result... So, while a certain programming style doesn't prevent you from a runtime exception, this behavior could simply represent the more likely outcome of an underlying logical problem, thus really helping to spot a hidden issue.

Enough of this all.
I concede that the visitor pattern can be useful indeed, as any other design pattern obviously :-)

Will read your talk, I'm sure it will be interesting and challenging.
Good luck for your work. My pleasure to have virtually met you here ;)

@nuttycom
Copy link
Author

nuttycom commented Jan 5, 2017

Heh. It's true - Double is a terrible type to use. Much better to use distinct Width and Height newtype wrappers - as you might see a discussion of in my talk!

@giuliohome
Copy link

giuliohome commented Jan 8, 2017

Real word apps are full of types like double, string, etc and they're useful when you have to deliver an app within a deadline and you don't have time to add tons of code without any additional feature from the user perspective. Anyway, there is another point and it is related to the state handling. Let me explain it below.

Oh ... and this is a big problem: with a switch statement I can manage a mutable state IShape that is now Rectangle and then Circle. I don't see how it could be done with a Visitor, since I should accept a generic IShape and that's against the Visitor pattern in the first place. Have a look at my latest repository and tell me if you see any way - using C# as a language - to replace this switch with a Visitor?

Thank you again!

@giuliohome
Copy link

Never mind! Found and commited the changes to switch to a visitor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment