Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java - Return a value from Visitor

Suppose we have following classes which we can't change:

interface Base {
    void accept(Visitor visitor);
}

class Foo implements Base {
    short getShortValue() {
        return 1;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Bar implements Base {
    int getIntValue() {
        return 2;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

interface Visitor {
    void visit(Foo foo);

    void visit(Bar bar);
}

And we need to implement method:

 int getValue(Base base)

There are many possibilities to do it with visitor using some storage object:

int useArray(Base base) {
    int[] result = new int[1];

    base.accept(new Visitor() {
        @Override
        public void visit(Foo foo) {
            result[0] = foo.getShortValue();
        }

        @Override
        public void visit(Bar bar) {
            result[0] = bar.getIntValue();
        }
    });

    return result[0];
}

int useAtomic(Base base) {
    AtomicInteger result = new AtomicInteger();

    base.accept(new Visitor() {
        @Override
        public void visit(Foo foo) {
            result.set(foo.getShortValue());
        }

        @Override
        public void visit(Bar bar) {
            result.set(bar.getIntValue());
        }
    });

    return result.intValue();
}

int useMutable(Base base) {
    MutableInteger result = new MutableInteger(0);

    base.accept(new Visitor() {
        @Override
        public void visit(Foo foo) {
            result.setValue(foo.getShortValue());
        }

        @Override
        public void visit(Bar bar) {
            result.setValue(bar.getIntValue());
        }
    });

    return result.getValue();
}

Or something perverted:

int useException(Base base) {
    class GotResult extends RuntimeException {
        private final int value;

        public GotResult(int value) {
            this.value = value;
        }
    }

    try {
        base.accept(new Visitor() {
            @Override
            public void visit(Foo foo) {
                throw new GotResult(foo.getShortValue());
            }

            @Override
            public void visit(Bar bar) {
                throw new GotResult(bar.getIntValue());
            }
        });
    } catch (GotResult result) {
        return result.value;
    }

    throw new IllegalStateException();
}

Or don't use visitor at all:

int useCast(Base base) {
    if (base instanceof Foo) {
        return ((Foo) base).getShortValue();
    }
    if (base instanceof Bar) {
        return ((Bar) base).getIntValue();
    }
    throw new IllegalStateException();
}

Are these our only options now? We have Java 8 (will have 9 soon enough) and still writing these ugly pieces of error-prone code. :)

like image 854
okutane Avatar asked Jul 22 '26 05:07

okutane


1 Answers

I agree that not being able to return a value from the visitor is terrible from a safe usage point of view.

To avoid the burden of the gymnastic you demonstrated above, your best option is to create a wrapper type that expose a sane API (based on the corrected visitor pattern) so that the dirty work is done only once: when converting a Base value to that wrapper type.

Here is how you could do it:

interface BaseW {

    interface Cases<X> {
        X foo(Foo foo);
        X bar(Bar bar);
    }

    <X> X match(Cases<X> cases);
    //or alternatively, use a church encoding:
    //<X> X match(Function<Foo, X> foo, Function<Bar, X> bar);

    default Base asBase() {
        return match(new Cases<Base>() {
            @Override
            public Base foo(Foo foo) {
                return foo;
            }

            @Override
            public Base bar(Bar bar) {
                return bar;
            }
        });
    }

    static BaseW fromBase(Base base) {
        return new Visitor() {
            BaseW baseW;
            {
                base.accept(this);
            }
            @Override
            public void visit(Foo foo) {
                baseW = new BaseW() {
                    @Override
                    public <X> X match(Cases<X> cases) {
                        return cases.foo(foo);
                    }
                };
            }

            @Override
            public void visit(Bar bar) {
                baseW = new BaseW() {
                    @Override
                    public <X> X match(Cases<X> cases) {
                        return cases.bar(bar);
                    }
                };
            }
        }.baseW;
    }

    static int useCorrectedVisitor(Base base) {
        return fromBase(base).match(new Cases<Integer>() {
            @Override
            public Integer foo(Foo foo) {
                return (int) foo.getShortValue();
            }

            @Override
            public Integer bar(Bar bar) {
                return bar.getIntValue();
            }
        });
        // or, if church encoding was used:
        // return fromBase(base).match(
        //          foo -> (int) foo.getShortValue(),
        //          bar -> bar.getIntValue()
        // );
    }
}

Now (shameless plug), if you don't mind using derive4j (a jsr 269 code generator) the above can be simplified a fair bit, as well as improving syntax:

@org.derive4j.Data // <- generate an BaseWs classe that allows handling
                   // of the interface as an algebraic data type.
interface BaseW {

    interface Cases<X> {
        X foo(Foo foo);
        X bar(Bar bar);
    }

    <X> X match(Cases<X> cases);

    default Base asBase() {
        return match(BaseWs.cases(f -> f, b -> b));
    }

    static BaseW fromBase(Base base) {
        return new Visitor() {
            BaseW baseW;
            {
                base.accept(this);
            }
            @Override
            public void visit(Foo foo) {
                baseW = BaseWs.foo(foo);
            }

            @Override
            public void visit(Bar bar) {
                baseW = BaseWs.bar(bar);
            }
        }.baseW;
    }

    static int useStructuralPatternMatching(Base base) {
        return BaseWs.caseOf(fromBase(base))
            .foo(foo -> (int) foo.getShortValue())
            .bar(Bar::getIntValue);
    }
}
like image 66
JbGi Avatar answered Jul 24 '26 18:07

JbGi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!