14

Guava 的Optional 模式很棒,因为它有助于消除 null 的歧义。当链的第一部分可能不存在时,该transform方法对于创建空安全方法链非常有帮助,但在链的其他部分不存在时则无用。

这个问题与Guava Optional 类型有关,当转换返回另一个 Optional 时,它提出了基本相同的问题,但针对不同的用例,我认为这可能不是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. 它的行为与当前实现完全相同,只是它不会将函数的结果包装在 Optional 中。实现 (for Present) 可能是:

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

的实现Absenttransform

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

如果有一种方法来处理返回的方法null而不是Optional处理遗留对象,那也很好。这样的方法就像transform只是简单地调用Optional.fromNullable函数的结果。

我很好奇是否有其他人遇到过这种烦恼并找到了很好的解决方法(不涉及编写自己的Optional课程)。我也很想听听 Guava 团队的消息或被指出与该问题相关的讨论(我在搜索中没有找到任何内容)。

4

2 回答 2

12

您正在寻找一些 Monad,但 Guava 的 Optional(与例如 Scala 的 Option 相反)只是一个 Functor。

函子是什么鬼?!

Functor 和 Monad 是一种盒子,一种包装一些值的上下文。包含一些 A 类型值的 Functor 知道如何应用函数 A => B 并将结果放回 Functor。例如:从 Optional 中取出一些东西,变换,然后回绕成 Optional。在函数式编程语言中,这种方法通常被命名为“map”。

莫娜……什么?

Monad 与 Functor 几乎是一样的,只是它消耗包裹在 Monad 中的函数返回值(A => Monad,例如 Int => Optional)。这种神奇的 Monad 方法通常被称为“flatMap”。

在这里您可以找到基本 FP 术语的非常棒的解释:http: //adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

函子和单子来了!

Java 8 中的 Optional 可以分为 Functor ( http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Function- ) 和 Monad (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-)。

很好的 mon(ad)olog,Marcin,但我该如何解决我的特定问题?

我目前正在开发一个使用 Java 6 的项目,昨天我编写了一些名为“Optionals”的帮助程序类,这为我节省了很多时间。

它提供了一些辅助方法,允许我将 Optional 转换为 Monads (flatMap)。

这是代码:https ://gist.github.com/mkubala/046ae20946411f80ac52

因为我项目的代码库仍然使用空值作为返回值,所以我引入了 Optionals.lift(Function),它可以用来将结果包装到 Optional 中。

为什么将结果提升为可选?为了避免传递给转换的函数可能返回 null 并且整个表达式将返回“null 的存在”的情况(顺便说一句,这对于 Guava 的 Optional 是不可能的,因为这个后置条件 -> 请参见https://code 的第 71 行。 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);
于 2014-06-25T23:35:52.483 回答
0

您可能已经弄清楚了,但是您可以.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);

不太方便,但似乎没有其他方法。

于 2013-10-26T13:39:37.717 回答