Рубрики
Без рубрики

Введение в Атласскую фугу

Узнайте о библиотеке Java от Atlassian под названием Fugue, которая предоставляет набор утилит для поддержки функционального программирования.

Автор оригинала: Alessio Stalla.

1. введение

Fugue – это библиотека Java от Atlassian; это набор утилит, поддерживающих Функциональное программирование .

В этой статье мы сосредоточимся на наиболее важных API-интерфейсах Fugues и рассмотрим их.

2. Начало Работы С Фугой

Чтобы начать использовать Fugue в наших проектах, нам нужно добавить следующую зависимость:


    io.atlassian.fugue
    fugue
    4.5.1

Мы можем найти самую последнюю версию Fugue на Maven Central.

3. Опция

Давайте начнем наше путешествие с рассмотрения класса Option , который является ответом на java.util.Необязательный.

Как мы можем догадаться по названию, Option’ – это контейнер, представляющий потенциально отсутствующее значение.

Другими словами, параметр | является либо Некоторым значением определенного типа, либо Нет :

Option none = Option.none();
assertFalse(none.isDefined());

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

Option maybe = Option.option(someInputValue);

3.1. Работа с картой

Одним из стандартных API функционального программирования является метод map () , который позволяет применять предоставленную функцию к базовым элементам.

Метод применяет предоставленную функцию к значению Option , если оно присутствует:

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

3.2. Опция и Нулевое значение

Помимо различий в именах, Atlassian сделал несколько вариантов дизайна для Option , которые отличаются от Optional ; давайте теперь рассмотрим их.

Мы не можем напрямую создать непустую Опцию , содержащую null значение :

Option.some(null);

Вышеизложенное вызывает исключение.

Однако мы можем получить его в результате использования карта() операция:

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

Это невозможно при простом использовании java.util.Необязательный.

3.3. Опция Является Итерационной

Параметр можно рассматривать как коллекцию, содержащую максимум один элемент, поэтому имеет смысл реализовать интерфейс Iterable .

Это значительно повышает совместимость при работе с коллекциями/потоками.

А теперь, например, можно объединить с другой коллекцией:

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

3.4. Возможность преобразования в поток

Поскольку параметр | является итерируемым, он также может быть легко преобразован в поток .

После преобразования экземпляр Stream будет иметь ровно один элемент, если опция присутствует, или ноль в противном случае:

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

3.5. java.util.Дополнительная совместимость

Если нам нужна стандартная Необязательная реализация, мы можем легко получить ее с помощью метода to Optional() :

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

3.6. Класс утилиты Options

Наконец, Fugue предоставляет некоторые служебные методы для работы с Option s в классе с подходящим именем Options .

Он содержит такие методы, как filter None для удаления пустых Опций из коллекции и flatten для превращения ing коллекции Опций в коллекцию закрытых объектов, отфильтровывая пустые Опции.

Кроме того, он имеет несколько вариантов метода lift , который поднимает Функцию B> в Функцию, Option> : B>

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

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

Это полезно, когда мы хотим передать функцию, которая не знает о Option , какому-либо методу, использующему Option .

Обратите внимание, что, как и метод map , lift не сопоставляет null с None :

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

4. Либо для Вычислений С Двумя Возможными Исходами

Как мы уже видели, класс Option позволяет нам функционально справляться с отсутствием значения.

Однако иногда нам нужно возвращать больше информации, чем “нет значения”; например, мы можем захотеть вернуть либо допустимое значение, либо объект ошибки.

Класс Либо охватывает этот вариант использования.

Пример Любой может быть Правильно или Левый, но никогда не оба одновременно .

По условию, правое-это результат успешного вычисления, в то время как левое-исключительный случай.

4.1. Построение либо

Мы можем получить экземпляр Либо , вызвав один из двух его статических заводских методов.

Мы вызываем right , если нам нужно Либо , содержащее Right значение :

Either right = Either.right("value");

В противном случае мы вызываем left :

Either left = Either.left(-1);

Здесь наше вычисление может либо возвращать строку или Целое число.

4.2. Использование либо

Когда у нас есть экземпляр Или , мы можем проверить, является ли он левым или правым, и действовать соответственно:

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

Что еще более интересно, мы можем цеплять операции, используя функциональный стиль:

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

4.3. Прогнозы

Главное, что отличает их от других монадических инструментов, таких как Option, Try, , – это тот факт, что часто они непредвзяты. Проще говоря, если мы вызовем метод map (), Либо не знает, работать ли с Левой или Правой стороной.

Вот тут-то и пригодятся прогнозы.

Левая и правая проекции-это зеркальные представления Либо , которые фокусируются на левом или правом значении соответственно:

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

В приведенном выше фрагменте кода, если Либо находится Слева, код ошибки декодирования SQL() будет применен к базовому элементу. Если Либо является Правильным, это не так. То же самое и наоборот при использовании правильной проекции.

4.4. Служебные методы

Как и в случае с Options , Fugue также предоставляет класс , полный утилит для Либо , и он называется именно так: Либо .

Он содержит методы фильтрации, приведения и итерации по коллекциям Либо s.

5. Обработка исключений с помощью Try

Мы завершаем наш тур по тем или иным типам данных в Fugue другой вариацией под названием Try .

Try похож на Либо , но отличается тем, что он предназначен для работы с исключениями.

Как Option и в отличие от Либо , Try параметризуется по одному типу, потому что тип “другой” фиксируется на Exception (в то время как для Option он неявно Void ).

Таким образом, Попытка может быть либо Успехом , либо Неудачей :

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

5.1. Создание экземпляра попытки

Часто мы не будем создавать Try явно как успех или неудачу; скорее, мы создадим его из вызова метода.

Checked.of вызывает данную функцию и возвращает Try , инкапсулируя ее возвращаемое значение или любое вызванное исключение:

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

Другой метод, Проверено.lift , принимает потенциально метательную функцию и поднимает ее в функцию, возвращающую Try :

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

5.2. Работа С Try

Как только у нас есть Try , три наиболее распространенные вещи, которые мы, возможно, в конечном итоге захотим с ним сделать, это:

  1. извлечение его ценности
  2. привязка некоторой операции к успешному значению
  3. обработка исключения с помощью функции

Кроме того, очевидно, что отбрасывание Try или передача его другим методам, вышеперечисленные три варианта-не единственные, которые у нас есть, но все остальные встроенные методы-это просто удобство по сравнению с этими тремя.

5.3. Извлечение успешного значения

Чтобы извлечь значение, мы используем метод getOrElse :

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

Он возвращает успешное значение, если оно присутствует, или некоторое вычисленное значение в противном случае.

Нет getorthow или подобного, но поскольку getOrElse не улавливает никаких исключений, мы можем легко написать его:

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

5.4. Цепочка Вызовов После Успеха

В функциональном стиле мы можем применить функцию к значению успеха (если оно присутствует), не извлекая его явно.

Это типичный метод map , который мы находим в Option , Либо и большинстве других контейнеров и коллекций:

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

Он возвращает Try , чтобы мы могли связать дальнейшие операции.

Конечно, у нас также есть flatMap разнообразие:

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

5.5. Восстановление После Исключений

У нас есть аналогичные операции отображения, которые работают за исключением Try (если есть), а не его успешного значения.

Однако эти методы отличаются тем, что их смысл заключается в том, чтобы восстановиться после исключения, т. Е. Создать успешный Попробуйте в случае по умолчанию.

Таким образом, мы можем создать новое значение с помощью recover :

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

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

Как мы видим, функция восстановления принимает исключение в качестве единственного аргумента.

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

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

assertTrue(failure.isFailure());

Аналог flatMap называется recover С :

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

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

6. Другие утилиты

Давайте теперь быстро взглянем на некоторые другие утилиты в Fugue, прежде чем мы закончим.

6.1. Пары

A Pair – это действительно простая и универсальная структура данных, состоящая из двух одинаково важных компонентов, которые Fugue называет left и right :

Pair pair = Pair.pair(1, "a");
        
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());

Fugue не предоставляет много встроенных методов в Pair s, кроме отображения и шаблона прикладного функтора.

Однако Pair s используются во всей библиотеке, и они легко доступны для пользовательских программ.

Реализация следующего бедного человека Lisp находится всего в нескольких нажатиях клавиш!

6.2. Единица измерения

Unit – это перечисление с одним значением, которое должно представлять “нет значения”.

Это замена возвращаемого типа void и Void класса, которая устраняет null :

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

Однако довольно удивительно, что Option не понимает Unit , рассматривая его как некоторое значение , а не как ничего.

6.3. Статические утилиты

У нас есть несколько классов, заполненных статическими служебными методами, которые нам не придется писать и тестировать.

Класс Functions предлагает методы, которые используют и преобразуют функции различными способами: композиция, применение, каррирование , частичные функции с использованием Option , слабая запоминание и так далее.

Класс Suppliers предоставляет аналогичный, но более ограниченный набор утилит для Supplier s, то есть функций без аргументов.

Итераторы и Итераторы , наконец, содержат множество статических методов для управления этими двумя широко используемыми стандартными интерфейсами Java.

7. Заключение

В этой статье мы дали обзор библиотеки фуг от Atlassian.

Мы не касались тяжелых классов алгебры, таких как Моноид и Полугруппы , потому что они не вписываются в общую статью.

Тем не менее, вы можете прочитать о них и многое другое в Fugue javadocs и исходном коде .

Мы также не коснулись ни одного из дополнительных модулей, которые предлагают, например, интеграцию с Guava и Scala.

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub – это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.