Как стать автором
Обновить

Комментарии 32

Зумеры изобрели монаду Either

НЛО прилетело и опубликовало эту надпись здесь

Почему зумеры-то? Лямбдам в Java уже 10 лет скоро.

НЛО прилетело и опубликовало эту надпись здесь

Try изоморфна Either

Окей, это все круто, что вы смогли элегантно обойти проблему. Но, зачем велосипед если есть vavr?

Вавр использует свой Option плохо сочетается с j.u.Optional, здесь же по максимуму АПИ совместимо со стандартной библиотекой.

По моему опыту — вполне сочетается. Подход простой — не используете Optional вообще, ибо при наличии более полноценного Option от vavr он обычно нафиг не нужен (за теми редкими исключениями, когда его кто-то возвращает другой). Не исключаю конечно, что проблемы бывают — но чтобы прям было плохо…

Верно, кстати ссылку на vavr я давал в конце статьи.

Есть как минимум пара различий между vavr и предлагаемой библиотекой

  • vavr перехватывает Throwable, в том числе исключения Error, моя библиотека - только Exception, выкидывая Error наружу, что соответствует общепринятым рекомендациям по обработке исключений.

  • в vavr сложно реализована работа с ресурсами (до восьми дополнительных классов билдеров), у меня - всем хорошо известный известный механизм try-with-resource

Вы либо делаете полноценную библиотеку со всеми плюхами вавр (а их там очень много), можете попробовать pr со своим виденьем try создать. Думаю, что вам тогда объяснят, почему vavr просто ловит throwable (это важное свойство ФП, чтобы функция была определена на всем диапазоне входного типа и одинаково себя вела - очень упрощает тестирование). Либо не паритесь, т.к. цеплять зависимость с одной монадой от одного физика в своей проект - это верный способ наговнокодить.

А что делать, если есть смысл всё-таки выкинуть исключение из цепочки преобразований, а не "отфильтровать ошибки"? Плюнуть на Stream ввиду невозможности сделать это в рамках его API?

На мой взгляд, кидаться исключениями из стримов это даже для классического императивного подхода не комильфо. В ФП есть неписанный принцип "если компилируется - должно работать". Хороший принцип.

Если следовать ФП-подходу, то нужно таки дотащить объект монады с ошибкой до специального места, где обрабатываются все исключения - и там уже делать что угодно.

Окей, тогда другой вопрос к автору - можем ли мы свернуть, к примеру, Stream<Try<T>> в Try<List<T>> (чтобы потом дотащить его до упомянутого специального места и уже там один раз дёрнуть orElseThrow)? С ходу по документации ничего подобного найти не удалось.

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

Если вам по бизнесу нужен фейлсейф, то в стриме вместо X -> Y вы делаете X -> Try<Y> и на выходе получаете List<Try<Y>>. По сути получаете сводку типа "9 объектов из 10 обработались нормально, с 1 из 10 возникли такие-то проблемы".

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

Спасибо, интересно. Оказывается Вы тоже думали об этой проблеме

Эм, серьёзно? Вот так просто взять и промолчать, если какой-то из ресурсов не закрылся.

protected final void closeResources() {
  List<AutoCloseable> list = resources.get();
  if (list == null || list.isEmpty()) {
    return;
  }
  list.forEach(c -> {
    try {
      c.close();
    } catch (Exception e) {
      //silently
    }
  });
  list.clear();
  resources.remove();
}

И проверка на null лишняя, список будет инициализирован в любом случае.

Виноват, вы смотрите не тот файл. Смотрите пожалуйста на

function/src/main/java/com/github/skopylov58/functional/Try.java

Файл, который Вы смотрели, из прежних итераций, я удалил их из репозитария.

Сергей, спасибо! Отличная идея и реализация!

неуспешный Try — в пустой поток.

Вообще говоря, в vavr это поведение тоже вызывает вопросы. Неуспешные случаи тоже иногда надо обрабатывать, если у вас на выходе 10 exceptions — то стрим из них был бы логичным. Ну хотя бы по выбору.

Посмотрел исходный код и у меня сразу возникло несколько вопросов.

  • Почему Try не интерфейс?

  • Почему Try наследуется от TryAutoClosable, который в свою очередь реализует AutoClosable, а не наоборот?

И ещё, я не уверен, что этот код нормальный:

public class TryAutoCloseable implements AutoCloseable {
    private static final ThreadLocal<List<AutoCloseable>> resources = ThreadLocal.withInitial(ArrayList::new);  
    
    protected void addResource(AutoCloseable autoCloseable) {
        resources.get().add(autoCloseable);
    }

Виноват, вы смотрите не тот файл. Смотрите пожалуйста на

function/src/main/java/com/github/skopylov58/functional/Try.java

Файл TryAutoCloseable из прежних итераций, я удалил его из git репозитария.

Данная задача решается одним единственным интерфейсом. Правда под другие задачи потребуются ещё интерфейсы, но суммарно это всё чище и проще предложенного.

Может опишите?

Пишете интерфейс с одним методом экземпляра, выбрасывающим эксепшн. Под ним пишете статический метод, берущий на вход ваш интерфейс и возвращающий false/null/чтоНибудьЕщё в случае возникновения исключения.

Всё, дальше радуетесь жизни и простоте. И не надо библиотеки сочинять.

Показалось похожим на CompletableFuture<T>, но с method-chaining'ом. Кстати, что делать, если .recover(this::planA) при восстановлении задействует внешний ресурс и на нём упал, не вернув его в исходное состояние?

Увы, я думаю эта проблема выходит за рамки данной скромной библиотеки ;)

Try по хорошему должен предоставлять метод с параметром Function<? super Exception, Try<A>>. Если не ошибаюсь, это комбинатор bind (>>=), который достает значение из монады и передает монадической функции.

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

И ещё заметил, при возникновении исключения обработчик не может положить в результат какое-нибудь значение, хотя такое может быть полезно

Особенно классно искать в таком подходе перформанс-проблемы: профиль будет показывать погоду.

Может быть потому что примеры игрушечные? Читаемость традиционного императивного стиля умирает в реальном коде где-то на 2-3 уровне вложенности for / if-else, особенно когда появляются всякие асинхронные вещи.

Мог бы плюсануть - плюсанул бы.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории