Около года весь Java-код, который я пишу, в значительной степени использует подходы функционального программирования. Это дает массу преимуществ, но описание этого нового стиля – тема для отдельной длинной статьи.
Теперь я хотел бы сосредоточиться на одном любопытном наблюдении: иногда OO предоставляет более удобный способ использования даже чисто FP-концепций, таких как монады.
Краткое введение
Монады – это очень удобный шаблон проектирования , часто используемый для представления особых состояний значений – потенциально отсутствующих значений ( Возможно
/|/Опция ) или результаты вычислений, которые могут завершиться неудачей (
Либо /|/Результат
).
Обычно такая монада может быть реализована с помощью Алгебраические типы данных , в частности, Типы сумм .
Чтобы проиллюстрировать концепцию, давайте представим, что мы реализуем Java 8 Необязательно
с нуля:
public interface Optional{ Optional map(Function super T, U> mapper); Optional flatMap(Function super T, Optional> mapper); ... //other methods }
Теперь нам нужны две реализации. Один будет заниматься делом, когда у нас нет никакой ценности:
public class Noneimplements Optional { public Optional map(Function super T, U> mapper) { return new None<>(); } public Optional flatMap(Function super T, Optional> mapper) { return new None<>(); } ... // other methods }
Другой будет заниматься этим делом, когда у нас будет ценность:
public class Someimplements Optional { private final T value; ... //constructor, etc. public Optional map(Function super T, U> mapper) { return new Some<>(mapper.apply(value)); } public Optional flatMap(Function super T, Optional> mapper) { return mapper.apply(value); } ... // other methods }
С практической точки зрения, любой Необязательный
в нашем приложении всегда будет экземпляром либо Некоторые
или Нет
, если мы не добавим больше реализаций (закрытые классы в Java 16+ решают эту проблему). Другими словами, Необязательный
представляет собой сумму типов Некоторые
и Нет
.
Как Насчет Сопоставления С Образцом?
Как упоминалось выше, алгебраические типы данных и монады – это понятия, которые широко используются в FP. Для обработки различных случаев (например, Нет
и Некоторые
как показано выше), Функциональное программирование использует сопоставление шаблонов . На практике это означает, что полученное значение проверяется на тип и затем обрабатывается соответствующим образом. Например, вот как такая ситуация обрабатывается в Rust :
fn try_division(dividend: i32, divisor: i32) { match checked_division(dividend, divisor) { None => println!("{} / {} failed!", dividend, divisor), Some(quotient) => { println!("{} / {} = {}", dividend, divisor, quotient) }, } }
Это выглядит понятно, читабельно и удобно. Проблема возникает, когда нам нужно выполнить более одной операции над возвращаемым значением, И эти операции также могут возвращать Необязательный
. Теперь у нас есть что-то подобное (давайте продолжим использовать синтаксис, подобный Rust, здесь):
match operation1(args...) { None => ..., Some(value1) => { match operation2(args...) { None => ..., Some(value2) => { //do more work }, } }, }
Как вы можете видеть, сопоставление с образцом работает отлично, и компилятор гарантирует, что программист каждый раз проверяет все возможные случаи. Излишне говорить, насколько это хорошо для надежности кода. Но писать и читать такой код – это боль.
Многие языки FP приняли так называемый синтаксис do
, чтобы сделать обработку таких случаев намного более краткой и читаемой, но это еще одна (длинная) история и источник жарких дебатов. Вместо этого я предлагаю рассмотреть другое решение, предложенное языками OO, в частности Java.
Унифицированный интерфейс
Как вы, наверное, уже заметили, в случае Java оба класса реализуют один и тот же интерфейс. Это означает, что мы можем просто вызывать методы экземпляра, не проверяя каждый раз конкретный тип:
operation1(args...) .flatMap(value1 -> operation2(args...));
Добавление дополнительных операций так же просто:
operation1(args...) .flatMap(value1 -> operation2(args...)) .flatMap(value2 -> operation3(args...)) ... .flatMap(valueN-1 -> operationN(args...));
Не имеет значения, сколько операций нам нужно составить вместе, код останется кратким, легким для чтения, написания и обслуживания.
Вывод
Конечно, любопытное наблюдение, показанное выше, не означает, что один язык/языки/парадигма/и т.д. Лучше другого. Вместо этого я пытаюсь показать, что мы, программисты, должны быть открыты, так как лучшее решение проблемы может прийти с неожиданной стороны.
Оригинал: “https://dev.to/siy/how-interfaces-may-eliminate-need-for-pattern-matching-sometimes-4od2”