Comments 33
На КДПВ героиня из неплохого аниме Ergo Proxy.
+10
: рука-лицо: опять? :)
-8
Вот мне всегда нравятся такие «замечательные» примеры.
А что делать, если всё таки нужна обработка отсутствия данных о employee?
А логирование?
А что делать, если всё таки нужна обработка отсутствия данных о employee?
А логирование?
+2
В Scala можно, например, коллектить в список Either[String, Error]
0
Очевидно, в этом случае придется проверять монаду на пустоту. Хотя да, согласен. В монаде из либы не хватает нормального механизма узнать на каком именно этапе у нас возникло бы npe.
0
Обработка исключительных ситуаций в стримах обсуждается, например, здесь.
0
Хочется это всё видеть в статье. Те примеры, что даны в этой статье достаточно просты и известны. Интересуют те самые детали, которых в статье нет.
+3
Я вот лично сам не знаю как лучше. На мой взгляд проблема вообще надумана. Надо просто везде кидать unchecked exception, если что-то пошло не так. Восстановление после исключения в потоке не так часто требуется. Я запросил из базы данных 10 записей, на пятой произошёл connection timeout к базе. Не надо возвращать пользователю первые четыре записи, надо сказать, что ошибка произошла. А то он решит, что записей всего четыре.
+1
Но это же не туториал по использованию Optional, таких руководств тоже предостаточно. Задача статьи была продемонстрировать, как реализован паттерн монады в разных классах, естественно, я не стал погружаться в детали использования этих классов в разных юзкейсах.
Если вам нужна особая обработка отсутствия объекта на каком-то конкретном шаге, возможно, такой код вам и не подойдёт. Но в подавляющем большинстве случаев, с которыми я сталкивался, это как раз бывает неважно.
Если вы имеете в виду логирование в функциональном коде, то его логичнее всего сделать либо внутри применяемых функций, либо (что ещё лучше) внутри монадического объекта, так как логирование само по себе побочный эффект.
Если вам нужна особая обработка отсутствия объекта на каком-то конкретном шаге, возможно, такой код вам и не подойдёт. Но в подавляющем большинстве случаев, с которыми я сталкивался, это как раз бывает неважно.
Если вы имеете в виду логирование в функциональном коде, то его логичнее всего сделать либо внутри применяемых функций, либо (что ещё лучше) внутри монадического объекта, так как логирование само по себе побочный эффект.
+2
Идею можно здесь посмотреть: fsharpforfunandprofit.com/posts/recipe-part2
0
Я пытался ответить на эти вопросы здесь
habrahabr.ru/post/262971
habrahabr.ru/post/262971
0
Стоит заметить, что если у вас уже есть готовый API для, например,
На самом деле, мне кажется, что это выглядит даже опрятнее, чем метод автора, так как не требуется добавлять какие-то непонятные дополнительные методы.
Company
или Department
, то вы не сможете добавлять методы, возвращающие Stream
и будет иметь смысл использовать что-то типа:List<String> streamEmployeeNames = companies.stream()
.map(Company::getDepartments).flatMap(Collection::stream)
.map(Department::getEmployees).flatMap(Collection::stream)
.map(Employee::getName)
.collect(toList());
На самом деле, мне кажется, что это выглядит даже опрятнее, чем метод автора, так как не требуется добавлять какие-то непонятные дополнительные методы.
+3
У меня в либе есть шорткат для этого:
List<String> streamEmployeeNames = StreamEx.of(companies)
.flatCollection(Company::getDepartments)
.flatCollection(Department::getEmployees)
.map(Employee::getName)
.toList();
+1
Совершенно верно, именно так и выглядел мой пример в своей первой версии. Но потом мне показалось, что эти дополнительные преобразования несколько затуманивают монадический паттерн, а задача была именно его здесь продемонстрировать. А так получается, что мы ушли от одного бойлерплейта, а пришли к другому: о)
+2
На самом деле создавать методы, возвращающие Stream — это рекомендации лучших собаководов в Java-8. Если вы можете модифицировать ваш класс-коллекцию, добавьте метод, возвращающий Stream. И назовите его не
getDepartmentsStream()
, а просто departments()
. Смотрите String.chars()
, BufferedReader.lines()
, Random.ints()
и т. д. Если у вас есть старый метод, возвращающий какой-нибудь List
или массив, и есть возможность прорефакторить, разумно удалить старый метод. Stream будет гибче и эффективнее. +3
Я бы сказал, что будет красивее и гибче. Насчет эффективнее можно поспорить)
0
Объясняю, почему это в целом эффективнее. Вот, скажем, у вас метод getDepartments() возвращает
List
. Поначалу вы внутри своего объекта хранили данные в ArrayList
и потому метод выглядел примерно как return Collection.unmodifiableList(list);
. Потом вы отрефакторили свой объект и решили, что удобнее втнутри хранить Map
name -> department. Тогда в этом методе придётся написать return new ArrayList<>(map.values())
. Пользователям же нашего метода далеко не всегда нужен именно List
. В одном месте мы вызываем getDepartments().toArray(new Department[0]);
, потому что дальше вызываем метод, которому требуется массив. В другом месте нам просто интересно, есть ли вообще хоть один элемент: if(!getDepartments().isEmpty())
, в третьем нам нужны на самом деле списки сотрудников, как в примере автора статьи. Во всех этих случаях копирование значений мэпки в список оказывается излишней тратой времени и памяти. Если же мы будем возвращать Stream, мы можем рефакторить внутренности класса в весьма широких пределах и лишнего копирования не потребуется. +2
А теперь задумаемся о смысле (жизни) к чему все это. Ключевое слово — динамическая композиция кода.
Т.н. «классический императивный стиль» соответствует статической композиции. Ветвистый код намертво прибит гвоздями к экрану изнутри, а его поведение определяется исключительно данными. Достоинства: простота и предопределенность. Недостаток — многописанина и сложности с более высоким уровнем абстракции: все должно быть записано явно.
В динамической композиции кода, из которой более чем полностью состоит это ваше функциональное программирование, последовательность действий выстраивается в процессе выполнения. Это позволяет абстрагироваться от конкретики и сконцентрироваться на результате действий (т.н. «декларативный стиль»), тогда как вся конкретика реализуется уже готовыми паттернами. Недостатки: сложность пошаговой отладки и чтения кода «с нуля».
Так что все эти модные акторы, монады, ФРП — способы динамической композиции кода.
Т.н. «классический императивный стиль» соответствует статической композиции. Ветвистый код намертво прибит гвоздями к экрану изнутри, а его поведение определяется исключительно данными. Достоинства: простота и предопределенность. Недостаток — многописанина и сложности с более высоким уровнем абстракции: все должно быть записано явно.
В динамической композиции кода, из которой более чем полностью состоит это ваше функциональное программирование, последовательность действий выстраивается в процессе выполнения. Это позволяет абстрагироваться от конкретики и сконцентрироваться на результате действий (т.н. «декларативный стиль»), тогда как вся конкретика реализуется уже готовыми паттернами. Недостатки: сложность пошаговой отладки и чтения кода «с нуля».
Так что все эти модные акторы, монады, ФРП — способы динамической композиции кода.
-3
Что-то я не понял вашу мысль. Где вы увидели динамическую композицию? Все действия (функции) известны на этапе компиляции, их «стыкуемость» друг с другом статически проверена с помощью системы типов — опять же на этапе компиляции. Да, если рассмотреть какой-нибудь функциональный, но всё же динамически типизированный JavaScript, аналогичные паттерны не будут поддерживаться статической системой типов, и там композиция кода действительно будет динамической, но это именно особенность системы типов языка, а не парадигмы ФП.
«Сложность пошаговой отладки» — странная формулировка применительно к ФП. В функциональном коде просто нечего «пошагово отлаживать», т.к. он не «пошаговый», а декларативный. Сложность чтения функционального кода — это миф и исключительно дело привычки.
«Сложность пошаговой отладки» — странная формулировка применительно к ФП. В функциональном коде просто нечего «пошагово отлаживать», т.к. он не «пошаговый», а декларативный. Сложность чтения функционального кода — это миф и исключительно дело привычки.
+3
Формально поведение кода всегда детерминировано, если вы про это. Однако для ФП связывание (композиция) может происходит в рантайме и может зависеть от сторонних факторов. В качестве примеров возьмите джавовский CompletableFuture или reactive programming. Сначала программа вычисляет результат композиции — некую комбинированную функцию, а затем данная функция применяется к набору данных и вычисляется конечный результат. Комбинированная функция состоит из множества блоков, но может быть скомпонована каждый раз по-разному — компоновка полностью контролируется рантаймом, отсюда и динамическая композиция. Для императивного подхода данный метод недоступен, поскольку вся композиция происходит на этапе компиляции. Не знаю, объяснил ли я мысль.
0
Ну представьте себе ветвистый вложенный-перевложенный if в процедурном коде. Там тоже есть множество блоков, конкретная последовательность выполнения которых на момент компиляции вам неизвестна и определяется состоянием. Чем это не «комбинированная функция из множества блоков» в ваших терминах? И она тоже компонуется каждый раз по-разному в рантайме. Да что там, в Java вы на этапе компиляции в большинстве случаев даже не знаете, какой именно код будет фактически исполнен при вызове метода у объектной переменной, потому что все методы (кроме статических, финальных и приватных) являются виртуальными.
+2
Сложность пошаговой отладки действительно имеется, но по факту это вопрос перепрошивки мозга. Нужна ли она, пошаговая отладка пайплайнов-то? Если у вас методы, используемые в
Насчёт чтения кода не соглашусь. Конечно, можно переусердствовать с функциональщиной, но корявый код можно написать в рамках любой концепции. Нормальный функциональный код читается легче, чем императивный, потому что читающий видит более высокие абстракции и не отвлекается на ненужные детали.
map
и filter
покрыты тестами, то отлаживать весь пайплайн не придётся. Здесь пошаговая отладка нужна даже меньше, чем в SQL-запросах (в которых без неё тоже как-то живут).Насчёт чтения кода не соглашусь. Конечно, можно переусердствовать с функциональщиной, но корявый код можно написать в рамках любой концепции. Нормальный функциональный код читается легче, чем императивный, потому что читающий видит более высокие абстракции и не отвлекается на ненужные детали.
+2
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { }
Подсветка синтаксиса хромает. Токен «extends»
0
Sign up to leave a comment.
Слово на букву «М», или Монады уже здесь