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

Как Интерфейсы Могут Устранить Необходимость В Сопоставлении Шаблонов (иногда)

Унифицированный интерфейс может заменить сопоставление с образцом. С пометкой java, новички.

Около года весь Java-код, который я пишу, в значительной степени использует подходы функционального программирования. Это дает массу преимуществ, но описание этого нового стиля – тема для отдельной длинной статьи.

Теперь я хотел бы сосредоточиться на одном любопытном наблюдении: иногда OO предоставляет более удобный способ использования даже чисто FP-концепций, таких как монады.

Краткое введение

Монады – это очень удобный шаблон проектирования , часто используемый для представления особых состояний значений – потенциально отсутствующих значений ( Возможно /|/Опция ) или результаты вычислений, которые могут завершиться неудачей ( Либо /|/Результат ).

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

Чтобы проиллюстрировать концепцию, давайте представим, что мы реализуем Java 8 Необязательно с нуля:

public interface Optional {
     Optional map(Function mapper);
     Optional flatMap(Function> mapper);
    ... //other methods
}

Теперь нам нужны две реализации. Один будет заниматься делом, когда у нас нет никакой ценности:

public class None implements Optional {
    public  Optional map(Function mapper) {
        return new None<>();
    }
    public  Optional flatMap(Function> mapper) {
        return new None<>();
    }
    ... // other methods
}

Другой будет заниматься этим делом, когда у нас будет ценность:

public class Some implements Optional {
    private final T value;
    ... //constructor, etc.

    public  Optional map(Function mapper) {
        return new Some<>(mapper.apply(value));
    }

    public  Optional flatMap(Function> 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”