class Evaluators {

    public static <T, R> Function<T, R> memoizeLast(Function<T, R> function) {
        AtomicReference<Pair<T, Either<R, Throwable>>> lastRef = new AtomicReference<>();
        return argument -> {
            Pair<T, Either<R, Throwable>> last = lastRef.get();
            if (last != null && Objects.equals(argument, last.getLeft())) {
                if (last.getRight().hasValue()) {
                    return last.getRight().getValue();
                }
                Throwable error = last.getRight().getError();
                if (error instanceof RuntimeException) {
                    throw (RuntimeException) error;
                }
                if (error instanceof Error) {
                    throw (Error) error;
                }
                throw new IllegalStateException(error);
            }
            try {
                R result = function.apply(argument);
                lastRef.set(Pair.of(argument, Either.ofValue(result)));
                return result;
            } catch (Throwable e) {
                lastRef.set(Pair.of(argument, Either.ofError(e)));
                throw e;
            }
        };
    }

    private  Evaluators();

}