Нулевая цепочка методов с необязательным

Шаблон Optional в Guava великолепен, так как он помогает устранить двусмысленность с нулевым значением. Метод transform очень полезен для создания нулевых цепочек методов, когда первая часть цепочки может отсутствовать, но бесполезен, когда другие части цепочки отсутствуют.

Этот вопрос связан с Необязательный тип Guava, когда преобразование возвращает другой Необязательный, который задает по существу тот же вопрос, но для другого варианта использования, который, я думаю, может не быть предполагаемым использованием Optional (обработка ошибок).

Рассмотрим метод Optional<Book> findBook(String id). findBook(id).transform(Book.getName) работает как положено. Если книга не найдена, мы получаем Absent<String>, если книга найдена, мы получаем Present<String>.

В общем случае, когда промежуточные методы могут возвращать null/absent(), кажется, нет элегантного способа связать вызовы в цепочку. Например, предположим, что у Book есть метод Optional<Publisher> getPublisher(), и мы хотели бы получить все книги, опубликованные издателем книги. Казалось бы, естественный синтаксис findBook(id).transform(Book.getPublisher).transform(Publisher.getPublishedBooks), однако это не удастся, потому что вызов transform(Publisher.getPublishedBooks) на самом деле вернет Optional<Optional<Publisher>>.

Кажется вполне разумным иметь transform()-подобный метод для Optional, который принимал бы функцию, возвращающую Optional. Он будет действовать точно так же, как текущая реализация, за исключением того, что он просто не будет заключать результат функции в необязательный. Реализация (для Present) может выглядеть так:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    return function.apply(reference);
}

Реализация для Absent не отличается от transform:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    checkNotNull(function);
    return Optional.absent();
}

Также было бы неплохо, если бы существовал способ обработки методов, которые возвращают null, а не Optional для работы с устаревшими объектами. Такой метод будет похож на transform, но просто вызовет Optional.fromNullable в результате функции.

Мне любопытно, сталкивался ли кто-нибудь еще с этой неприятностью и нашел ли хорошие обходные пути (которые не включают в себя написание собственного класса Optional). Я также хотел бы услышать от команды Guava или указать на обсуждения, связанные с проблемой (я не нашел ничего в своем поиске).


person Yona Appletree    schedule 31.05.2013    source источник
comment
Пробовали ли вы участвовать в группе обсуждения? Рассмотрите возможность публикации там, даже если просто указать на этот (хорошо написанный) вопрос.   -  person Paul Bellora    schedule 03.07.2013


Ответы (2)


Вы ищете какую-то монаду, но необязательный элемент Guava (в отличие, например, от варианта Scala) — это просто функтор.

Что, черт возьми, за Functor?!

Функтор и монада — это своего рода коробка, контекст, который оборачивает какое-то значение. Функтор, содержащий какое-то значение типа А, знает, как применить функцию А => В и поместить результат обратно в Функтор. Например: получить что-то из необязательного, преобразовать и завернуть обратно в необязательное. В функциональных языках программирования такой метод часто называют map.

Мона... что?

Монада — это почти то же самое, что и Functor, за исключением того, что она использует функцию, возвращающую значение, обернутое в монаду (A => Monad, например, Int => Optional). Этот волшебный метод Монады часто называют «flatMap».

Здесь вы можете найти действительно потрясающие объяснения основных терминов FP: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Функторы и монады скоро появятся!

Необязательный из Java 8 может быть классифицирован как Functor (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Функция-) и Монада (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-).

Хороший мон(ад)олог, Марчин, но как я могу решить мою конкретную проблему?

В настоящее время я работаю над проектом, использующим Java 6, и вчера я написал вспомогательный класс под названием «Дополнительно», что сэкономило мне много времени.

Он предоставляет вспомогательный метод, который позволяет мне превратить необязательный объект в монады (flatMap).

Вот код: https://gist.github.com/mkubala/046ae20946411f80ac52

Поскольку кодовая база моего проекта по-прежнему использует нули в качестве возвращаемого значения, я ввел Optionals.lift(Function), который можно использовать для переноса результатов в необязательный.

Зачем поднимать результат в необязательный? Чтобы избежать ситуации, когда функция, переданная в преобразование, может вернуть значение null, а все выражение вернет «присутствие нуля» (что, кстати, невозможно с опцией Guava из-за этого постусловия -> см. строку № 71 в https).://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71).

Несколько примеров

Предположим, что findEntity() возвращает Optional, а Entity.getDecimalField(..) может возвращать BigDecimal или null:

Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    new Function<Entity, Optional<BigDecimal>> () {
        @Override 
        public Optional<BigDecimal> apply(Entity input) {
            return Optional.fromNullable(input.getDecimalField(..));
        }
    }
);

Еще один пример, предполагающий, что у меня уже есть функция, которая извлекает десятичные значения из сущностей и может возвращать нули:

Function<Entity, Decimal> extractDecimal = .. // extracts decimal value or null
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    Optionals.lift(extractDecimal)
);

И последнее, но не менее важное - ваш вариант использования в качестве примера:

Optional<Publisher> maybePublisher = Optionals.flatMap(findBook(id), Optionals.lift(Book.getPublisher));

// Assuming that getPublishedBooks may return null..
Optional<List<Book>> maybePublishedBooks = Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks));

// ..or simpler, in case when getPublishedBooks never returns null
Optional<List<Book>> maybePublishedBooks2 = maybePublisher.transform(Publisher.getPublishedBooks);

// as a one-liner:
Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks)).transform(Publisher.getPublishedBooks);
person Community    schedule 25.06.2014

Вы, вероятно, поняли это, но вы могли бы добавить .or(Optional.absent) после каждого преобразования, которое возвращает Optional (в вашем случае после .transform(Book.getPublisher), уменьшая Optional<Optional<T>> до Optional<T>:

Optional<List<Book>> publishedBooks = findBook(id).transform(Book.getPublisher).
        or(Optional.absent()).transform(Publisher.getPublishedBooks);

К сожалению, здесь нельзя вывести тип Optional.absent, поэтому фактически код выглядит так:

Optional<List<Book>> publishedBooks = book.transform(Book.getPublisher).
        or(Optional.<Publisher> absent()).transform(Publisher.getPublishedBoooks);

Не слишком удобно, но другого выхода, похоже, нет.

person siledh    schedule 26.10.2013