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

К лучшему функциональному программированию на Java с различными

Функциональной парадигме в программировании более 62 лет, впервые представленной на языке Lisp в… Помеченный java, var, функциональный.

Функциональной парадигме в программировании более 62 лет, впервые представленной на языке Lisp в 1958 1 . В контексте языка Java он начал широко использоваться относительно недавно с точки зрения этих лет, потому что с версии 1.8 или 6 лет назад. Итак, что такое функциональное программирование, можем ли мы функционально программировать на Java, можем ли мы согласовать эту парадигму с ООП и как библиотека Vavr может помочь нам во всем этом?

Начиная с самого начала

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

Наиболее важным предположением функционального программирования (FP) является отсутствие так называемых побочных эффектов, когда существует множество подходов, решений или языковых функций, которые приближают нас к этой цели, и они включают:

  • Неизменность
  • Ленивая инициализация
  • Декларативный язык программирования
  • Первоклассные функции
  • Функции более высокого порядка

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

Что касается компонента FP, отсутствия побочных эффектов, то это ситуация, когда данный вызов функции не вызывает побочных эффектов в системе и сохраняет так называемую ссылочную прозрачность. Это означает, ни больше, ни меньше, что мы должны иметь возможность заменить вызов нашей функции для заданных входных параметров ее значением. Другими словами, функция должна быть детерминированной для заданных ей аргументов и всегда должна возвращать один и тот же результат, не вызывая никаких изменений в состоянии системы, кроме области действия функции.

Однако это не означает, что мы должны полностью отказаться от операций мутации, в конце концов, было бы невозможно написать систему, основанную на бизнес-требованиях, без базы данных, кэша или даже http-запросов. Главное, чтобы действия, изменяющие какое-либо состояние, были извлечены, насколько это возможно, должны быть явно помечены и в идеале выполняться в одной точке кодовой базы, в идеале в основном классе или каком-либо выделенном модуле.

Функциональное программирование на Java?

Распространенной жалобой на Java является ее простой синтаксис и отсутствие так называемого синтаксического сахара. Объем кода, необходимый для написания данной функциональности, по сравнению с языками, которые ставят функциональную парадигму на первое место в своих основах 2 , определенно больше. Также верно, что эти языки имеют более высокий порог входа, чем Java, определенно легче найти программиста на Java, чем Clojure или Scala, что часто влияет на основной язык, на котором будет написан проект. Кроме того, Java составляет огромный процент текущего рынка, многие проекты были написаны или находятся в процессе написания на Java, и часто было бы невозможно переписать их на новый язык с лучшими языковыми конструкциями, чтобы воспользоваться преимуществами использования парадигмы FP. Проще говоря – иногда у нас нет возможности изменить язык, и все же мы хотели бы попробовать работать с новой парадигмой. Именно поэтому, начиная с Java версии 8 и благодаря конструкциям, реализованным в библиотеке Var 3 , это желание становится осуществимым.

Вавр

Var , ранее JavaSlang – это библиотека, доступная по лицензии Apache 2.0, которая предоставляет нам инструменты, облегчающие функциональное программирование с использованием Java. Стоит отметить, что Var не содержит никаких зависимостей от внешних библиотек и основан исключительно на API, предлагаемом Java. Благодаря лямбда-выражениям 4 , функциональные интерфейсы 5 , ключевое слово var 6 наряду с использованием компонентов из библиотеки Var становится возможным функциональное программирование на Java. Сама библиотека состоит из логически разделенных модулей, таких как: gwt, match, test, jackson, gson, render, которые были реализованы вокруг основного основного модуля, и мы рассмотрим выбранные элементы этого модуля.

Страшные монады и как их обнять

Чтобы лучше понять концепции, реализованные в библиотеке Var, нам понадобится другое определение – определение Монады. Это зловеще звучащее название, которое уходит своими корнями в Теорию категорий 7, для целей этой статьи и для упрощения его можно разбить на простое для понимания описание – Монада – это контейнер для данных, который позволяет нам декларативно оперировать данными, используя операции, общие для других Монад. Существует высокая вероятность того, что если вы раньше не сталкивались с этим термином, вы невольно использовали сами Монады, например, используя Монаду, представленную в JDK 1.8 – Необязательный класс 8. В контексте описания, представленного ранее, Необязательный класс является контейнером для данных или их отсутствия и предоставляет набор API, позволяющий выполнять декларативные операции с этими данными, включая, среди прочего, операции:

карта – включение функции для выполнения над заданными данными плоская карта – включение функции для выполнения над данными, которые уже являются монадой, другими словами, выравнивание данных фильтр – отфильтровывание данных, которые нам не интересны поток 9 – преобразование из необязательной Монады в поток 10 Монада

Стоит отметить, что, как уже упоминалось, аналоги этих операций также можно найти в других Монадах, поэтому, когда нам приходится работать с Монадой, поставляемой из данной библиотеки, мы должны иметь возможность вызывать функции для данных, сглаживать данные, фильтровать данные или преобразовывать в другую Монаду. Это, конечно, лишь некоторые из операций, доступных на монадах, и доступный API сильно зависит от поставщика библиотеки или собственного творческого изобретения автора.

Переходя к сути, библиотека Var предоставляет нам 5 основных монад, и это:

Любой

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

Вариант

Монада используется в ситуации, когда мы явно хотим выразить намерение, при котором вызов нашего метода может вернуть результат или отсутствие результата. Основное преимущество в отличие от использования аннотации @Nullable 11 или Javadoc, описывающей возможный нулевой результат, заключается в том, что мы описали ситуацию с использованием объекта, который является частью сигнатуры метода, и, таким образом, обработка ситуации, когда результат не будет присутствовать, требуется и проверяется компилятором. Наиболее важное различие между Option и Optional заключается в том, что функция сопоставления, возвращающая значение null, вызовет исключение. Кроме того, эта Монада предоставляет более богатый набор API-интерфейсов, включая метод fold, который позволяет нам выполнять операцию сопоставления значений при ее отсутствии или наличии. Примеры использования опции:

В представленной реализации модуль Книги обеспечивает операцию поиска информации о книге на основе ISBN с использованием метода findBookDetails, который использует опцию Monad в своей подписи. Пользователь API обязан справиться с ситуацией, в которой данные о книге недоступны через тип, содержащийся в подписи, в противном случае он получит ошибку компиляции.

Пример реализации операций на основе API опций вместе с любым API выглядит следующим образом:

Где: Строки 11-12 обрабатывают потенциальные нулевые значения из объекта ISBN, переданного пользователем нашего API, чтобы далее в строке 13 выполнить http-запрос, который вернет результат, завернутый в любую монаду. Все обрабатывается в строке 14, где при отсутствии значений из предыдущих вызовов мы возвращаем пустой результат, а в случае наличия данных и вызова запроса мы обрабатываем результат, сопоставляя любую монаду с параметром Монада и вызывая метод toDto из объекта BookData.

Попробуй

Monad Try аналогичен Option Monad, за исключением того, что в отличие от пропущенных значений он хранит исключение, которое могло возникнуть при вызове функции. Стоит рассмотреть возможность использования этой монады вместо использования проверенного исключения или вместо того, чтобы создавать исключения во время выполнения и перехватывать их с помощью механизмов фреймворка. Вместо этого мы можем рассматривать наши исключения как стандартные объекты и регулярно обрабатывать ошибки или откладывать их обработку в выбранном месте. Вместо этого мы можем рассматривать наши исключения как стандартные объекты и регулярно обрабатывать ошибки или откладывать их обработку в выбранном месте.

Где: Строка 16 начинает использовать Try, обертывая вызов метода getBookData из класса библиотеки стороннего книжного магазина сигнатурой проверенного исключения. Благодаря библиотеке Vavr мы можем двигаться дальше и отказаться от императивного стиля – определения блока try catch – к декларативному стилю при обработке исключения. В строке 17, в случае исключения, вызванного методом getBookData, мы регистрируем ситуацию, затем в строке 18 мы преобразуем в любую монаду, вызывая mapLeft в строке 19, в которой мы переводим исключение из библиотеки в понятное внутри нашей системы.

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

Ленивый

Монада Lazy представляет значение, полученное не на этапе инициализации, а в момент обращения к самому значению, в данном контексте мы называем это ленивой инициализацией. Его работу можно сравнить с известным поставщиком интерфейса Java 8, который возвращает заданное значение, с той разницей, что Lazy Monad реализовал функциональность запоминания и отвечает вышеупомянутой ссылочной прозрачности, а это означает, что для одного и того же набора аргументов Monad вычисляет результат только один раз и сохраняет его, возвращая вычисленное значение для дальнейших вызовов. Пример использования Ленивой монады:

Где: В строке 5 мы передали зависимость, возвращающую список доступных книг, операция извлечения доступных книг была определена вне класса библиотеки и является дорогостоящей, поскольку требует вызова нескольких HTTP-запросов. Используя Ленивую монаду, мы откладываем вызов дорогостоящих вызовов до первого вызова метода getAvailableBooks, и этот результат сохраняется благодаря ранее упомянутой функции запоминания.

Будущее

Последняя из Монад, представленных в библиотеке Vavr – Будущая Монада представляет ценность, результат которой будет доступен где-то во времени. Операции с этой монадой не являются блокирующими. По своему поведению он напоминает классы Future/CompletableFuture, известные из JDK. Пример использования будущей Монады:

Где: Строка 13 преобразует переданную коллекцию наборов в потоковую монаду Строка 14 вызывает метод “Найти сведения о книге”, который завершает вызов метода службы книг в будущем.вызова метода с использованием введенной зависимости исполнителя Строка 15 собирает результат, преобразует его в список, а затем результат передается методу последовательности из будущего класса Последние строки 16 и 17 отвечают за сбор результата и создание нового доступного объекта результатов книг и возврат его в будущей монаде. Таким образом, мы добились функциональности, с помощью которой мы можем, не блокируя, загружать информацию о доступных книгах.

Кратко о коллекциях и шаблонах соответствие

Когда дело доходит до модификации состояния, упомянутого много раз, у вас может сложиться непреодолимое впечатление, что у нас есть проблема в мире Java. Эта проблема заключается в отсутствии неизменяемых коллекций. Все они изменяют внутреннее состояние, и когда дело доходит до неизменяемого представления методов из класса Collections, они только делают нашу коллекцию доступной только для чтения, чего во многих случаях может быть недостаточно. В случае, если мы хотели бы, чтобы наша коллекция была полностью неизменяемой, библиотека Var предоставляет нам решение в виде недавно определенных “Функциональных структур данных”, которые включают интерфейсы Seq, Set и Map, все реализующие общий интерфейс – Итеративный. Обзор реализаций, доступных в библиотеке Vavr, выходит далеко за рамки этой статьи, в конце концов, на момент написания этой статьи у нас было доступно 15 реализаций, но я настоятельно рекомендую вам прочитать их документацию об этом.

Еще одним элементом, представленным в Обширной библиотеке и часто являющимся частью многих функциональных языков 12, является сопоставление с образцом. Проще говоря, сопоставление шаблонов позволяет декларативно определять управление на основе заданного условия, так называемого “предиката соответствия”. Пример использования сопоставления с образцом из библиотеки Var выглядит следующим образом:

Где: Строка 2 Запускает сопоставление шаблонов, состоящих из предикатов из строк со 2 по 5, и когда: Экземпляр объекта Number имеет тип BigDecimal, он вызывает и возвращает результат метода add из BigDecimal Экземпляр объекта Number имеет тип BigInteger, он вызывает и возвращает результат метода add из класса BigInteger. Экземпляр объекта Number имеет целочисленный тип, суммирует значение и возвращает результат. Экземпляр объекта Number имеет тип Float, суммирует значение и возвращает результат.

В дополнение к предикатам предоставленная библиотекой Var, вы можете определить свои собственные предикаты и использовать их с механизмом, представленным выше, используя модуль vavr-match .

Стоит добавить, что сопоставление шаблонов типов постепенно внедряется в Java. Выражения переключения были добавлены в JDK 14 13 что позволяет использовать оператор switch также в качестве выражений, кроме того, в версии JDK 14 авторы ввели сопоставление шаблонов для экземпляра инструкции в предварительной версии, которая была дополнительно перенесена в версию JDK 15 во второй предварительной версии 14 .

Резюме

Несмотря на то, что ему 62 года, функциональное программирование работает очень хорошо и не забыто, но является парадигмой, которая используется ежедневно. Языки все более и более смело адаптируют функциональную парадигму, и программисты замечают преимущества ее использования. Мир Java и ООП не обязательно исключает использование FP в существующих или новых проектах, синтаксис Java с годами улучшался, поэтому вместе с использованием библиотек, поддерживающих функциональное программирование и реализованные там концепции, в настоящее время у нас может возникнуть соблазн ввести новую парадигму в нашу кодовую базу. Это позволит нам лучше абстрагироваться от побочных эффектов, поможет нам создать код, устойчивый к проблемам многопоточности, код, который более предсказуем и, следовательно, лучше поддерживается с течением времени. В статье я включил основные концепции, касающиеся FP, и кратко представил использование отдельных элементов библиотеки Vavr, которые являются лишь кратким введением во весь спектр решений и подходов, которые могут быть реализованы. Если вы впервые столкнулись с подходами, описанными в статье, я настоятельно рекомендую вам продолжить изучение предмета, потому что эти знания определенно окупятся в будущем и могут дать другую точку зрения на определенные проблемы.

1 Для тех, кто заинтересован, я представляю статью , описывающую историю функциональных языков. 2 В мире JVM это будет, например, Scala или Clojure . 3 Для Java существуют другие библиотеки, облегчающие функциональное программирование, такие как Cyclops или Функциональная Java , однако статья посвящена исключительно библиотеке Vavr. 4 Добавлено в JDK 1.8, JEP 126 . 5 Добавлено в JDK 1.8 . 6 Добавлено в JDK 11, JEP 286 . Ключевое слово var не требуется для функционального подхода, но оно помогает, например, при чтении сильно вложенных монад. 7 Для тех, кто заинтересован, я оставляю ссылку на страницу GitHub с источником книги “Теория категорий для программистов”, написанной Бартошем Милевским. Книга в PDF-версии опубликована под лицензией GNU GPL v.3. 8 В контексте законов Монады Необязательный класс не соответствует всем критериям, необходимым для того, чтобы считаться истинной Монадой, в то время как для целей этой статьи я принял упрощенное сравнение. 9 Метод добавлен в JDK 9. 10 Опять же, для целей статьи я предположил, что Поток класс отвечает всем законам Монады, на практике этот класс не является полноценной Монадой, подробнее можно прочитать в этой статье . 11 Хотя текущие IDE способны улавливать такие ошибки и существуют плагины , которые могут анализировать наши аннотации и на этапе сборки позволяют фиксировать такие места, в то время как это внешние инструменты, и вы должны позаботиться об их соответствующей конфигурации. 12 Сопоставление с шаблоном Scala , Сопоставление с шаблоном Хаскелла . 13 JEP 361 . 14 JEP 305 и JEP 375 .

Оригинал: “https://dev.to/sonalake/towards-better-functional-programming-in-java-with-vavr-3dem”