Автор оригинала: 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
3.1. Работа с картой
Одним из стандартных API функционального программирования является метод map () , который позволяет применять предоставленную функцию к базовым элементам.
Метод применяет предоставленную функцию к значению Option , если оно присутствует:
Optionsome = Option.some("value") .map(String::toUpperCase); assertEquals("VALUE", some.get());
3.2. Опция и Нулевое значение
Помимо различий в именах, Atlassian сделал несколько вариантов дизайна для Option , которые отличаются от Optional ; давайте теперь рассмотрим их.
Мы не можем напрямую создать непустую Опцию , содержащую null значение :
Option.some(null);
Вышеизложенное вызывает исключение.
Однако мы можем получить его в результате использования карта() операция:
Option
Это невозможно при простом использовании java.util.Необязательный.
3.3. Опция Является Итерационной
Параметр можно рассматривать как коллекцию, содержащую максимум один элемент, поэтому имеет смысл реализовать интерфейс Iterable .
Это значительно повышает совместимость при работе с коллекциями/потоками.
А теперь, например, можно объединить с другой коллекцией:
Optionsome = 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
3.6. Класс утилиты Options
Наконец, Fugue предоставляет некоторые служебные методы для работы с Option s в классе с подходящим именем Options .
Он содержит такие методы, как filter None для удаления пустых Опций из коллекции и flatten для превращения ing коллекции Опций в коллекцию закрытых объектов, отфильтровывая пустые Опции.
Кроме того, он имеет несколько вариантов метода lift , который поднимает Функцию B> в Функцию, Option> : B>
Functionf = (Integer x) -> x > 0 ? x + 1 : null; Function
Это полезно, когда мы хотим передать функцию, которая не знает о Option , какому-либо методу, использующему Option .
Обратите внимание, что, как и метод map , lift не сопоставляет null с None :
assertEquals(null, lifted.apply(Option.some(0)).get());
4. Либо для Вычислений С Двумя Возможными Исходами
Как мы уже видели, класс Option позволяет нам функционально справляться с отсутствием значения.
Однако иногда нам нужно возвращать больше информации, чем “нет значения”; например, мы можем захотеть вернуть либо допустимое значение, либо объект ошибки.
Класс Либо охватывает этот вариант использования.
Пример Любой может быть Правильно или Левый, но никогда не оба одновременно .
По условию, правое-это результат успешного вычисления, в то время как левое-исключительный случай.
4.1. Построение либо
Мы можем получить экземпляр Либо , вызвав один из двух его статических заводских методов.
Мы вызываем right , если нам нужно Либо , содержащее Right значение :
Eitherright = Either.right("value");
В противном случае мы вызываем left :
Eitherleft = 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.FunctionthrowException = (String x) -> { throw new Exception(x); }; assertTrue(Checked.lift(throwException).apply("ko").isFailure());
5.2. Работа С Try
Как только у нас есть Try , три наиболее распространенные вещи, которые мы, возможно, в конечном итоге захотим с ним сделать, это:
- извлечение его ценности
- привязка некоторой операции к успешному значению
- обработка исключения с помощью функции
Кроме того, очевидно, что отбрасывание Try или передача его другим методам, вышеперечисленные три варианта-не единственные, которые у нас есть, но все остальные встроенные методы-это просто удобство по сравнению с этими тремя.
5.3. Извлечение успешного значения
Чтобы извлечь значение, мы используем метод getOrElse :
assertEquals(42, failedTry.getOrElse(() -> 42));
Он возвращает успешное значение, если оно присутствует, или некоторое вычисленное значение в противном случае.
Нет getorthow или подобного, но поскольку getOrElse не улавливает никаких исключений, мы можем легко написать его:
someTry.getOrElse(() -> { throw new NoSuchElementException("Nothing to get"); });
5.4. Цепочка Вызовов После Успеха
В функциональном стиле мы можем применить функцию к значению успеха (если оно присутствует), не извлекая его явно.
Это типичный метод map , который мы находим в Option , Либо и большинстве других контейнеров и коллекций:
TryaTry = Try.successful(42).map(x -> x + 1);
Он возвращает Try , чтобы мы могли связать дальнейшие операции.
Конечно, у нас также есть flatMap разнообразие:
Try.successful(42).flatMap(x -> Try.successful(x + 1));
5.5. Восстановление После Исключений
У нас есть аналогичные операции отображения, которые работают за исключением Try (если есть), а не его успешного значения.
Однако эти методы отличаются тем, что их смысл заключается в том, чтобы восстановиться после исключения, т. Е. Создать успешный Попробуйте в случае по умолчанию.
Таким образом, мы можем создать новое значение с помощью recover :
Try
Как мы видим, функция восстановления принимает исключение в качестве единственного аргумента.
Если функция восстановления сама выбрасывает, результатом является еще одна неудачная попытка Try :
Try
Аналог flatMap называется recover С :
Try
6. Другие утилиты
Давайте теперь быстро взглянем на некоторые другие утилиты в Fugue, прежде чем мы закончим.
6.1. Пары
A Pair – это действительно простая и универсальная структура данных, состоящая из двух одинаково важных компонентов, которые Fugue называет left и right :
Pairpair = 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, поэтому его должно быть легко импортировать и запускать как есть.