News for hackers. Stuff that matters.
Re:The visitor pattern is essentially the same thing as Chur... by _old_dude_ on Thursday February 04, 2021 @06:47AM (#26024566)
In Java,

  interface Shape<T> {
    interface _Circle<T> {
      T apply(int x, int y, int r);
    }
    interface _Rectangle<T> {
      T apply(int x, int y, int w, int h);
    }
    T apply(_Circle<T> circle, _Rectangle<T> rectangle);

    static <T> Shape<T> Circle(int x, int y, int r) {
      return (circle, rectangle) -> circle.apply(x, y, r);
    }
    static <T> Shape<T> Rectangle(int x, int y, int w, int h) {
      return (circle, rectangle) -> rectangle.apply(x, y, w, h);
    }

    static double area(Shape<?> shape) {
      var s = (Shape<Double>) shape;
      return s.apply(
          (x, y, r) -> Math.PI * r * r,
          (x, y, w, h) -> 1. * w * h
      );
    }

    static void main(String[] args) {
      var exampleCircle = Circle(2, 1, 4);
      var exampleRectangle = Rectangle(1, 3, 10, 7);

      System.out.println(area(exampleCircle));
      System.out.println(area(exampleRectangle));
    }
  }
BTW, you can use the same encoding when you write a Promise in JS.
Re:The visitor pattern is essentially the same thing as Chur... by Twisol on Thursday February 04, 2021 @06:52AM (#26024585)
As with the TypeScript examples, we can make the top-level apply method generic instead of the whole class to avoid some casts. In particular, notice that the <T> inferred from calling Circle() and Rectangle() is the unmeaningful <Object>.

The downside is that Java doesn't let you implement generic methods with lambdas, which is silly, so you have to use an anonymous subclass. But at least the call-side uses stay the same.

(And then, combining the `_Circle` and `_Rectangle` interfaces gets you to a more canonical visitor pattern, at the expense of lambdas at the call-site.)

    public interface Shape {
      interface _Circle<T> {
        T apply(int x, int y, int r);
      }
      interface _Rectangle<T> {
        T apply(int x, int y, int w, int h);
      }

      <T> T apply(_Circle<T> circle, _Rectangle<T> rectangle);

      static Shape Circle(int x, int y, int r) {
        return new Shape() {
          public <T> T apply(_Circle<T> circle, _Rectangle<T> rectangle) {
            return circle.apply(x, y, r);
          }
        };
      }
      static Shape Rectangle(int x, int y, int w, int h) {
        return new Shape() {
          public <T> T apply(_Circle<T> circle, _Rectangle<T> rectangle) {
            return rectangle.apply(x, y, w, h);
          }
        };
      }

      static double area(Shape shape) {
        return shape.apply(
            (x, y, r) -> Math.PI * r * r,
            (x, y, w, h) -> 1. * w * h
        );
      }

      static void main(String[] args) {
        var exampleCircle = Circle(2, 1, 4);
        var exampleRectangle = Rectangle(1, 3, 10, 7);

        System.out.println(area(exampleCircle));
        System.out.println(area(exampleRectangle));
      }
    }
Re:The visitor pattern is essentially the same thing as Chur... by _old_dude_ on Thursday February 04, 2021 @07:00AM (#26024626)
> The downside is that Java doesn't let you implement generic methods with lambdas ...

yes, that exactly why i've chosen to make the type parametric and not the method apply, but you're right that the classical visitor pattern uses a polymorphic method (accept) not a polymorphic class.

Re:The visitor pattern is essentially the same thing as Chur... by Twisol on Thursday February 04, 2021 @07:02AM (#26024638)
Sure. Then, if you're set on using lambdas, I'd recommend this, to at least take away the ability to choose <T> from the caller.

    static Shape<?> Circle(int x, int y, int r) {
      return (circle, rectangle) -> circle.apply(x, y, r);
    }
    static Shape<?> Rectangle(int x, int y, int w, int h) {
      return (circle, rectangle) -> rectangle.apply(x, y, w, h);
    }
Re:The visitor pattern is essentially the same thing as Chur... by dfgdghdf on Thursday February 04, 2021 @08:19AM (#26025067)
This might get messy once you need hashCode and equals
Re:The visitor pattern is essentially the same thing as Chur... by _old_dude_ on Thursday February 04, 2021 @09:03AM (#26025371)
You can keep the Circle and Rectangle as class (record here) and only see them as Shape when you want to do the double dispatch of the visitor.

  interface Shape {
    record Circle(int x, int w, int r) {}
    record Rectangle(int x, int y, int w, int h) {}

    <T> T apply(Function<Circle, T> _circle, Function<Rectangle, T> _rectangle);

    static <T> Shape shape(Circle circle) {
      return new Shape() {
        @Override
        public <T> T apply(Function<Circle, T> _circle, Function<Rectangle, T> _rectangle) {
          return _circle.apply(circle);
        }
      };
    }
    static <T> Shape shape(Rectangle rectangle) {
      return new Shape() {
        @Override
        public <T> T apply(Function<Circle, T> _circle, Function<Rectangle, T> _rectangle) {
          return _rectangle.apply(rectangle);
        }
      };
    }

    static double area(Shape shape) {
      return shape.apply(
          circle -> Math.PI * circle.r * circle.r,
          rectangle -> 1. * rectangle.w * rectangle.h
      );
    }

    static void main(String[] args) {
      var aCircle = new Circle(2, 1, 4);
      var aRectangle = new Rectangle(1, 3, 10, 7);

      System.out.println(area(shape(aCircle)));
      System.out.println(area(shape(aRectangle)));
    }
  }