Introduction to Atlassian Fugue – Atlassian Fugue简介

最后修改: 2018年 4月 4日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Fugue is a Java library by Atlassian; it’s a collection of utilities supporting Functional Programming.

Fugue是Atlassian的一个Java库;它是一个支持功能编程的实用程序集合。

In this write-up, we’ll focus on and explore the most important Fugue’s APIs.

在这篇文章中,我们将关注和探索Fugue最重要的API。

2. Getting Started With Fugue

2.赋格的入门

To start using Fugue in our projects, we need to add the following dependency:

为了在我们的项目中开始使用Fugue,我们需要添加以下依赖关系。

<dependency>
    <groupId>io.atlassian.fugue</groupId>
    <artifactId>fugue</artifactId>
    <version>4.5.1</version>
</dependency>

We can find the most recent version of Fugue over on Maven Central.

我们可以在Maven中心找到Fugue的最新版本。

3. Option

3.选择

Let’s start our journey by looking at the Option class which is Fugue’s answer to java.util.Optional.

让我们从Option类开始,它是Fugue对java.util.Optional.的回应。

As we can guess by the name, Option’s a container representing a potentially absent value.

正如我们从名字中可以猜到的那样,Option’是一个容器,代表一个可能不存在的值。

In other words, an Option is either Some value of a certain type or None:

换句话说,一个Option要么是某个特定类型的值,要么是None

Option<Object> none = Option.none();
assertFalse(none.isDefined());

Option<String> some = Option.some("value");
assertTrue(some.isDefined());
assertEquals("value", some.get());

Option<Integer> maybe = Option.option(someInputValue);

3.1. The map Operation

3.1.地图操作

One of the standard Functional Programming APIs is the map() method which allows applying a provided function to underlying elements.

标准的功能编程API之一是map()方法,它允许将提供的函数应用于底层元素。

The method applies the provided function to the Option‘s value if it’s present:

该方法将提供的函数应用于Option的值,如果它存在的话。

Option<String> some = Option.some("value") 
  .map(String::toUpperCase);
assertEquals("VALUE", some.get());

3.2. Option and a Null Value

3.2.选项和一个

Besides naming differences, Atlassian did make a few design choices for Option that differ from Optional; let’s now look at them.

除了命名上的差异,Atlassian确实为Option做了一些与Optional不同的设计选择;现在让我们来看看它们。

We cannot directly create a non-empty Option holding a null value:

我们不能直接创建一个非空的Option持有null

Option.some(null);

The above throws an exception.

上述情况会抛出一个异常。

However, we can get one as a result of using the map() operation:

然而,我们可以通过使用map()操作得到一个结果:

Option<Object> some = Option.some("value")
  .map(x -> null);
assertNull(some.get());

This isn’t possible when simply using java.util.Optional.

当简单地使用java.util.Optional.时,这是不可能的。

3.3. Option Is Iterable

3.3.选项IIterable

Option can be treated as a collection that holds maximum one element, so it makes sense for it to implement the Iterable interface.

Option可以被视为一个最多容纳一个元素的集合,所以它实现Iterable接口是合理的。

This highly increases the interoperability when working with collections/streams.

这大大增加了在使用集合/流时的互操作性。

And now, for example, can be concatenated with another collection:

而现在,例如,可以与另一个集合串联起来。

Option<String> some = Option.some("value");
Iterable<String> strings = Iterables
  .concat(some, Arrays.asList("a", "b", "c"));

3.4. Converting Option to Stream

3.4.将Option转换为Stream

Since an Option is an Iterable, it can be converted to a Stream easily too.

由于Option是一个Iterable,它也可以很容易地转换为一个Stream

After converting, the Stream instance will have exactly one element if the option is present, or zero otherwise:

转换后,如果选项存在,Stream实例将有确切的一个元素,否则就是零。

assertEquals(0, Option.none().toStream().count());
assertEquals(1, Option.some("value").toStream().count());

3.5. java.util.Optional Interoperability

3.5.java.util.Optional 互操作性

If we need a standard Optional implementation, we can obtain it easily using the toOptional() method:

如果我们需要一个标准的Optional实现,我们可以使用toOptional()方法轻松获得它。

Optional<Object> optional = Option.none()
  .toOptional();
assertTrue(Option.fromOptional(optional)
  .isEmpty());

3.6. The Options Utility Class

3.6.选项工具类

Finally, Fugue provides some utility methods for working with Options in the aptly named Options class.

最后,Fugue在命名为Options的类中提供了一些处理Options的实用方法。

It features methods such as filterNone for removing empty Options from a collection, and flatten for turning a collection of Options into a collection of enclosed objects, filtering out empty Options.

它的特点是:filterNone用于从一个集合中移除空的Options,以及flatten用于将Options集合变成一个封闭对象的集合,过滤掉空的Options

Additionally, it features several variants of the lift method that lifts a Function<A,B> into a Function<Option<A>, Option<B>>:

此外,它还具有几个lift方法的变体,可以将一个Function<A,B>提升为一个Function<Option<A>, Option<B>>

Function<Integer, Integer> f = (Integer x) -> x > 0 ? x + 1 : null;
Function<Option<Integer>, Option<Integer>> lifted = Options.lift(f);

assertEquals(2, (long) lifted.apply(Option.some(1)).get());
assertTrue(lifted.apply(Option.none()).isEmpty());

This is useful when we want to pass a function which is unaware of Option to some method that uses Option.

当我们想把一个不知道Option的函数传递给某个使用Option的方法时,这很有用。

Note that, just like the map method, lift doesn’t map null to None:

注意,就像map方法一样,lift不会将null映射到None

assertEquals(null, lifted.apply(Option.some(0)).get());

4. Either for Computations With Two Possible Outcomes

4.对于有两种可能结果的计算,Either

As we’ve seen, the Option class allows us to deal with the absence of a value in a functional manner.

正如我们所看到的,Option类允许我们以一种功能性的方式来处理没有值的问题。

However, sometimes we need to return more information than “no value”; for example, we might want to return either a legitimate value or an error object.

然而,有时我们需要返回比 “无值 “更多的信息;例如,我们可能想返回一个合法的值或一个错误对象。

The Either class covers that use case.

Either 类涵盖了该用例。

An instance of Either can be a Right or a Left but never both at the same time.

一个Either的实例可以是RightLeft,但不能同时是

By convention, the right is the result of a successful computation, while the left is the exceptional case.

按照惯例,右边是成功计算的结果,而左边是特殊情况。

4.1. Constructing an Either

4.1.构建一个Either

We can obtain an Either instance by calling one of its two static factory methods.

我们可以通过调用它的两个静态工厂方法之一来获得一个Either实例。

We call right if we want an Either containing the Right value:

如果我们想要一个包含Right值的Either,我们调用right:。

Either<Integer, String> right = Either.right("value");

Otherwise, we call left:

否则,我们称之为left

Either<Integer, String> left = Either.left(-1);

Here, our computation can either return a String or an Integer.

在这里,我们的计算可以要么返回一个字符串,要么整数

4.2. Using an Either

4.2.使用一个Either

When we have an Either instance, we can check whether it’s left or right and act accordingly:

当我们有一个Either 实例时,我们可以检查它是左还是右,并采取相应的行动。

if (either.isRight()) {
    ...
}

More interestingly, we can chain operations using a functional style:

更有趣的是,我们可以使用函数式的连锁操作。

either
  .map(String::toUpperCase)
  .getOrNull();

4.3. Projections

4.3.预测

The main thing that differentiates Either from other monadic tools like Option, Try, is the fact that often it’s unbiased. Simply put, if we call the map() method, Either doesn’t know if to work with Left or Right side.

Either与其他单体工具(如Option, Try,)的主要区别在于,通常它是无偏见的。简单地说,如果我们调用map()方法,Either不知道是要与Left还是Right边工作。

This is where projections come in handy.

这就是预测的用武之地。

Left and right projections are specular views of an Either that focus on the left or right value, respectively:

左边和右边的投影是一个Either的镜面视图,分别聚焦于左边或右边的值

either.left()
  .map(x -> decodeSQLErrorCode(x));

In the above code snippet, if Either is Left, decodeSQLErrorCode() will get applied to the underlying element. If Either is Right, it won’t. Same the other way around when using the right projection.

在上面的代码片段中,如果EitherLeft,decodeSQLErrorCode()将被应用到基础元素。如果EitherRight,它不会。当使用右边的投影时,反之亦然。

4.4. Utility Methods

4.4.实用方法

As with Options, Fugue provides a class full of utilities for Eithers, as well, and it’s called just like that: Eithers.

Options一样,Fugue也为Eithers提供了一个充满实用程序的类,它的名字就是这样的。Eithers

It contains methods for filtering, casting and iterating over collections of Eithers.

它包含了对Eithers集合进行过滤、铸造和迭代的方法。

5. Exception Handling with Try

5.用Try处理异常情况

We conclude our tour of either-this-or-that data types in Fugue with another variation called Try.

我们用另一种叫做Try的变体来结束我们在Fugue中的非此即彼的数据类型之旅。

Try is similar to Either, but it differs in that it’s dedicated for working with exceptions.

TryEither类似,但它的不同之处在于,它专门用于处理异常情况。

Like Option and unlike Either, Try is parameterized over a single type, because the “other” type is fixed to Exception (while for Option it’s implicitly Void).

OptionEither不同,Try在一个单一的类型上被参数化,因为 “其他 “类型被固定为Exception(而对于Option它是隐含的Void)。

So, a Try can be either a Success or a Failure:

因此,一个尝试可以是成功失败

assertTrue(Try.failure(new Exception("Fail!")).isFailure());
assertTrue(Try.successful("OK").isSuccess());

5.1. Instantiating a Try

5.1.实例化一个Try

Often, we won’t be creating a Try explicitly as a success or a failure; rather, we’ll create one from a method call.

通常,我们不会明确地创建一个Try作为成功或失败;相反,我们会从方法调用中创建一个。

Checked.of calls a given function and returns a Try encapsulating its return value or any thrown exception:

Checked.of调用一个给定的函数并返回一个Try封装其返回值或任何抛出的异常。

assertTrue(Checked.of(() -> "ok").isSuccess());
assertTrue(Checked.of(() -> { throw new Exception("ko"); }).isFailure());

Another method, Checked.lift, takes a potentially throwing function and lifts it to a function returning a Try:

另一个方法,Checked.lift,采取了一个潜在的抛出函数,lift它是一个返回Try的函数。

Checked.Function<String, Object, Exception> throwException = (String x) -> {
    throw new Exception(x);
};
        
assertTrue(Checked.lift(throwException).apply("ko").isFailure());

5.2. Working With Try

5.2.使用Try工作

Once we have a Try, the three most common things we might ultimately want to do with it are:

一旦我们有了Try,我们最终可能想用它做的三件最常见的事情是。

  1. extracting its value
  2. chaining some operation to the successful value
  3. handling the exception with a function

Besides, obviously, discarding the Try or passing it along to other methods, the above three aren’t the only options that we have, but all the other built-in methods are just a convenience over these three.

很明显,除了丢弃Try或将其传递给其他方法之外,上述三种方法并不是我们唯一的选择,其他所有的内置方法都只是在这三种方法之上提供了便利。

5.3. Extracting the Successful Value

5.3.提取成功值

To extract the value, we use the getOrElse method:

为了提取该值,我们使用getOrElse方法。

assertEquals(42, failedTry.getOrElse(() -> 42));

It returns the successful value if present, or some computed value otherwise.

如果存在,它返回成功的值,否则返回一些计算的值。

There is no getOrThrow or similar, but since getOrElse doesn’t catch any exception, we can easily write it:

没有getOrThrow或类似的东西,但由于getOrElse不捕捉任何异常,我们可以很容易地写出来。

someTry.getOrElse(() -> {
    throw new NoSuchElementException("Nothing to get");
});

5.4. Chaining Calls After Success

5.4.成功后的连锁调用

In a functional style, we can apply a function to the success value (if present) without extracting it explicitly first.

在函数式中,我们可以对成功值(如果存在的话)应用一个函数,而不用先明确地提取它。

This is the typical map method we find in Option, Either and most other containers and collections:

这是我们在OptionEither和大多数其他容器和集合中发现的典型map方法。

Try<Integer> aTry = Try.successful(42).map(x -> x + 1);

It returns a Try so we can chain further operations.

它返回一个Try ,这样我们就可以进行进一步的连锁操作。

Of course, we also have the flatMap variety:

当然,我们也有flatMap变量。

Try.successful(42).flatMap(x -> Try.successful(x + 1));

5.5. Recovering From Exceptions

5.5.从异常中恢复

We have analogous mapping operations that work with the exception of a Try (if present), rather than its successful value.

我们有类似的映射操作,除了Try (如果存在),而不是它的成功值。

However, those methods differ in that their meaning is to recover from the exception, i.e. to produce a successful Try in the default case.

然而,这些方法的不同之处在于,它们的意义在于从异常中恢复,即在默认情况下产生一个成功的Try

Thus, we can produce a new value with recover:

因此,我们可以用recover产生一个新值。

Try<Object> recover = Try
  .failure(new Exception("boo!"))
  .recover((Exception e) -> e.getMessage() + " recovered.");

assertTrue(recover.isSuccess());
assertEquals("boo! recovered.", recover.getOrElse(() -> null));

As we can see, the recovery function takes the exception as its only argument.

我们可以看到,恢复函数将异常作为其唯一的参数。

If the recovery function itself throws, the result is another failed Try:

如果恢复函数本身抛出,结果是另一个失败的Try

Try<Object> failure = Try.failure(new Exception("boo!")).recover(x -> {
    throw new RuntimeException(x);
});

assertTrue(failure.isFailure());

The analogous to flatMap is called recoverWith:

flatMap类似的是recoverWith

Try<Object> recover = Try
  .failure(new Exception("boo!"))
  .recoverWith((Exception e) -> Try.successful("recovered again!"));

assertTrue(recover.isSuccess());
assertEquals("recovered again!", recover.getOrElse(() -> null));

6. Other Utilities

6.其他实用工具

Let’s now have a quick look at some of the other utilities in Fugue, before we wrap it up.

在我们结束之前,让我们快速浏览一下Fugue中的其他一些工具。

6.1. Pairs

6.1.对子

A Pair is a really simple and versatile data structure, made of two equally important components, which Fugue calls left and right:

对子是一个非常简单和通用的数据结构,由两个同样重要的组件组成,Fugue称之为

Pair<Integer, String> pair = Pair.pair(1, "a");
        
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());

Fugue doesn’t provide many built-in methods on Pairs, besides mapping and the applicative functor pattern.

除了映射和应用漏斗模式外,Fugue并没有在Pairs上提供很多内置方法。

However, Pairs are used throughout the library and they are readily available for user programs.

然而,Pairs在整个图书馆中使用,它们可以随时供用户程序使用。

The next poor person’s implementation of Lisp is just a few keystrokes away!

下一个穷人对Lisp的实现就在几个按键之间!”。

6.2. Unit

6.2.单位

Unit is an enum with a single value which is meant to represent “no value”.

Unit是一个具有单一值的枚举,旨在代表 “无值”。

It’s a replacement for the void return type and Void class, that does away with null:

它是void返回类型和Void类的替代品,它取消了null

Unit doSomething() {
    System.out.println("Hello! Side effect");
    return Unit();
}

Quite surprisingly, however, Option doesn’t understand Unit, treating it like some value instead of none.

然而,令人惊讶的是,Option并不理解Unit,它把它当作一些值,而不是没有。

6.3. Static Utilities

6.3.静态实用工具

We have a few classes packed full of static utility methods that we won’t have to write and test.

我们有几个类,里面装满了静态的实用方法,我们不需要编写和测试。

The Functions class offers methods that use and transform functions in various ways: composition, application, currying, partial functions using Option, weak memoization et cetera.

Functions类提供了以各种方式使用和转换函数的方法:组合、应用、咖喱、使用Option的部分函数、弱备忘等等。

The Suppliers class provides a similar, but more limited, collection of utilities for Suppliers, that is, functions of no arguments.

Suppliers类为Suppliers,也就是没有参数的函数,提供了一个类似的,但更有限的实用程序集合。

Iterables and Iterators, finally, contain a host of static methods for manipulating those two widely used standard Java interfaces.

IterablesIterators,最后,包含了大量的静态方法来操作这两个广泛使用的标准Java接口。

7. Conclusion

7.结论

In this article, we’ve given an overview of the Fugue library by Atlassian.

在这篇文章中,我们对Atlassian的Fugue库做了一个概述。

We haven’t touched the algebra-heavy classes like Monoid and Semigroups because they don’t fit in a generalist article.

我们还没有触及像MonoidSemigroups这样的重度代数类,因为它们不适合在一篇通识文章中出现。

However, you can read about them and more in the Fugue javadocs and source code.

然而,你可以在Fugue javadocs源代码中阅读到它们以及更多的信息。

We also haven’t touched on any of the optional modules, that offer for example integrations with Guava and Scala.

我们还没有触及任何可选的模块,例如提供与Guava和Scala的集成。

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as is.

所有这些例子和代码片段的实现都可以在GitHub项目中找到–这是一个Maven项目,所以应该很容易导入并按原样运行。