Pull to refresh

Comments 38

Спасибо большое за библиотеку и примеры. В который раз убеждаюсь, что функциональное программирование в Java - боль и страдания. Код читать очень тяжело. На ревью, попадись такое, я бы орал (но ревьюил, а что делать). Спасибо что есть Котлин, там это все красивее

Я бы эту библиотеку на ревью просто не пропустил. Причина здесь несколько:

  • Плохая читаемость кода.

  • Инструмент поставлен вперед задачи. Задачи из примеров прекрасно решаются прощее/понятнее с использованием стандартных инструментов Java. А на Kotlin это была бы вообще песня.

  • Хорошее API удобно использовать, лучшие API сложно использовать неправильно. Из того что я увидел, можно ожидать очень странных конструкций, особенно от разработчиков уровня Junior/Middle.

P.S. Часть функций вообще провоцируют антипаттерны. В частности dispatch из примера это конкретный код за который хочется очень обстоятельно пояснить почему это плохо.

Уменьшить страдание я и хотел. Тема о том, "а вот в Хаскеле это намного круче" конечно интересная, но я собственно адресую к тем, кому не шашечки, а ехать. Java так Java.

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

О! Спасибо что напомнили о стримах.

Сколько раз вы сталкивались с необходимостью получить индекс объекта в стриме?

Вот как это можно сделать с помощью карринга и SupplierExt

        List.of(10,20,30,40,50,60,70,80,90,100)
                .stream().forEach(Consumer2.of((AtomicInteger i,Integer x)->{
                    System.out.println(i.get()+"*"+x+"="+(i.getAndIncrement()*x));
                }).acceptArg1(SupplierExt.ofConst(new AtomicInteger())));
    }

Заметьте, AtomicInteger нигде вне контекста не доступен, и никто не сможет как то исказить данные.

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

Аналогичный код в Java парадигме
        List<Integer> numbers = Arrays.asList(12, 23, 37, 45, 58, 64, 79, 83, 91, 102); // Произвольный список чисел

        IntStream.range(0, numbers.size()) // Создаем поток индексов от 0 до размера списка
                .forEach(i -> System.out.println(i + "*" + numbers.get(i) + "=" + (i * numbers.get(i))));

Если че это код сгенерировал ChatGPT. На порядок легче в поддержке.

Оптимизация кода (от ChatGPT)

  1. Использование стандартных интерфейсов: Замените Consumer2 и SupplierExt на стандартные интерфейсы Java для упрощения и улучшения поддержки кода.

  2. Избегание глобального состояния: Код использует AtomicInteger для учета индекса внутри лямбда-выражения, что не является идеальным для функционального стиля, где предпочтительнее избегать изменяемого состояния. Вместо этого мы можем использовать метод IntStream для генерации индексов.

  3. Эффективное использование Stream API: Метод forEach предназначен для операций без возврата значения, но в вашем случае выглядит, что вы хотите использовать значения индексов, что лучше делать через метод map.

Вот ровно про это я и писал что эта библиотека провоцирует написание плохого кода

Ну а вы, видимо, считаете, что раскрыли мне глаза.

Ну модифицируйте теперь этот код для двух индексов, трех, четырех.

Я порадуюсь за вас, как вы все это будете вытягивать через один параметр.

Мои расширения, конечно, не дают почти никаких преимуществ на числе параметров 1 и 2, которые уже реализованы стандартно, а у меня нужны исключительно для связности.

Я не пишу код ради кода. Я пишу код под задачу. Есть желание потягаться - я не против. Ставьте задачу и посмотрим у кого код лучше.

Ну, вообще говоря, в стриме нет естественного индекса объекта. Если вам хочется - сделайте стрим из пар (индекс, объект), либо как вы поступили, либо zipWithIndex. И имейте при этом в виду, что как только вы начнете обработку стрима параллелить, ваш атомик возможно станет бутылочным горлышком :)

Ну атомик бутылочным горлышком редко становится. Поскольку синхронизацию не использует. Тут важна сама идея захвата объекта в контексте функции, а не вне ее.

Против идеи ничего не имею, идея норм. Я про то, что стримы (в отличие от циклов) хорошо параллелятся, но не всегда, а скажем если в них нет вот таких вот операций.

Когда мне нужно получить индекс объекта в стриме, я обычно использую старый добрый for. Работает безотказно.

Присоединяюсь к первому комментарию.
Благо есть Котлин, — если есть возможность, переходите на него)

То что в Котлин ФП реализовано получше я не спорю. Было бы и странно, если не так. Но, в защиту Java, ФП в нее было добавлено поверх грандиозной легаси. Добавлено с полным сохранением обратной совместимости кода. А это подвиг почти.

Вспомните, любители Скалы, танцы с бубнами вокруг перехода на версию 2.1

Честно, я прочитал уже пару десятков статей про ФП, и везде каррирование сопровождается каким-то патологически плохим примером. Сначала идут дифирамбы этому приему, а потом берут какую-то нормальную функцию и превращают ее в ФП-абракадабру. Я решительно не понимаю ни где это может помочь мне в моей рутине, ни зачем вообще этим заниматься. Про монады, функторы, функции высшего порядка - все кристально ясно (и они реально популярны на практике), а вот с каррированием какая-то шляпа.

зачем вообще этим заниматься

Затем что это упрощает некоторые вещи с точки зрения функций высших порядков. Потому что иметь везде унарные функции местами удобнее. Ну например map - вас же не смущает, что у map в сигнатуре унарная функция, вместо многих разных map с функциями разной -арности?

Про пример ничего сказать не хочу, вполне допускаю, что игрушечный пример и правда плох. И я готов согласиться, что придумать хороший пример и правда сложно, и что каррирование это не то, что применяется на повседневной основе - все скорее правда.

Но неясного-то в нем что?

Идите знаете куда со своим каррированием? В Scala идите) А если серьезно, то статья интересная для расширения кругозора, но надо писать, что все выполнено профессионалами и повторять такое в проде дома не надо.

С каррированием лучше в Кложур, если уж на то. ;)

Да нет, Scala вполне себе дружит с каррированием, функциями высшего порядка и прочей функциональщиной, причем из коробки. При этом пытаясь усидеть также и на ООП. В остальном, Scala или Clojure - это на вкус и цвет.

Скала пытается усидеть сразу на всем. И в этом ее беда. Кложур же является диалектом Лиспа, и карриривание просто естественная часть его жизненного цикла разработки. У него другая проблема, как и у любого Лиспа. Недостаток выразительных средств. Все терема высекаются одним топором.

Вообще "зачем все это нужно" вопрос правильный, но заслуживающий не стали даже, а книги целой. И, сдается мне, они уже написаны. В двух словах, последовательное применение FP сильно облегчает написание Data Oriented программ. То есть, таких программ, где из за сложности данных, с которыми приходится работать, сама по себе парадигма ООП становится антипатерном.

Моя цель была именно остаться в пределах ванильной Java. Конечно, любой может объявить какой угодно функциональный интерфейс. Для этого библиотеки не нужны. Но реальная польза от него возникает только если одновременно создается вся цепочка интерфейсов, связанных друг с другом. А это лишняя работа, которая делать не хочется.

И тут полезно иметь какой то шаблон, а не переизобретать велосипед каждый раз.

Скала пытается усидеть сразу на всем. И в этом ее беда

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

а не переизобретать велосипед каждый раз

Кажется, что ваше решение как раз напоминает велосипед

К теме как раз не относится ни Скала, ни Котлин, ни F#. Потому что есть корпоративная реальность. Вы приходите на проект и вам сообщают, что писать надо на Java. Ваше блеяние о том, что Котлин лучше никого из менеджмента не интересует. Соответственно, цель всего этого - выжать максимум из того что доступно в данном конкретном инструменте.

Вернитесь к первоначальному комментарию, про Скалу это шутка была. А что не шутка, так это то, что не надо такое тащить в прод, если это не стартап или ваш пет-проект. На джаве пишут обычно энтерпрайз разной степени кровавости. И в нем очень важна читабельность и поддерживаемость кода, часто важнее производительности и лаконичности. А такие "выжимания максимума" это как раз то, что делает энтерпрайз кровавым. Вы поразвлекаетесь и уйдете в другое место писать на Clojure, а кому-то разгребать потом ваши каррирования. Как обучающе-развлекательная статья в стиле "видали чё можно на вашей Джаве" - норм.

А чем, мне правда интересно, читабельность хуже? Она просто такая какая есть. Нут ничего изменить нельзя. Если и функции 10 параметров, то в Java вынь да положь перечислить их все. включая подтипы.

И что непонятного в, например, applyArg1(value)?

Функция вернула функцию. Обычный прием. Только на кор джава в разы больше набирать. Вот уж где точно с читабельностью полный затык будет.

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

Если и функции 10 параметров, то в Java вынь да положь перечислить их все. включая подтипы.

Никто не мешает отрефакторить и сделать, чтобы функция принимала меньше параметров. А параметры сделать полями объекта с builder. Это делается одной аннотацией Lombok. И тогда вместо applyArg1(value) будет нормальное имя поля и также передавать можно не все поля. Если такая функция из библиотеки, то можно написать к ней свой адаптер, как вариант. Если все параметры не нужны, можно сделать перегруженный метод и т.д.

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

Два момента:

1) сборка мусора. Параметры функции передаются через стек, поля объекта создаются в куче. Начните только профайлинг. Много интересного узнаете.

2) Ад бесконтрольного роста количества классов с единственной целью, удовлетворить какую то одну функцию. От функции то вы ведь все равно не избавитесь. Так как она работу делает. А лишний класс - это зло.

3) По хорошему, а нахрена теперь Ломбок, если есть рекорды? У меня мало в каких проектах применяется. Ей Богу, когда меня начинают убеждать, что сроки сорваны из за набора сеттеров и геттеров, которые все равнo IDE сгенерировала, то не знаешь, то ли смеяться, то ли по ушам настучать.

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

И что? Сборщик мусора замечательно справляется. В любом джава-приложении создается огромное число классов в том числе даже самой JVM и это не является проблемой. В общем, похоже на экономию на спичках. Если у вас высоконагруженное приложение с лимитом по памяти, то на java в принципе не надо его писать.

Ад бесконтрольного роста количества классов с единственной целью удовлетворить какую то одну функцию

Он вполне контролируемый) И цель тут сделать код читаемым. Если вам кажется, что applyArg1 и applyArg2 понятнее чем setUser и setEmail, ну ок. В любом подходе надо включать голову, конечно же.

а нахрена теперь Ломбок, если есть рекорды?

Я ж не знаю какая у вас Java. Рекорды еще лучше, да. Правда, с ними билдер сложно сделать. А если 10 параметров в конструктор пихать, то зачем такой рекорд нужен.

Ей Богу, когда меня начинают убеждать, что сроки сорваны из за набора сеттеров и геттеров, которые все равнo IDE сгенерировала

Не очень понял при чем тут это, но не суть. Вы просили пояснить почему мне кажется, что каррирование и остальное - не очень, я пояснил. Дальше спорить не вижу смысла

Вам повезло не писать код для сервисов с миллиардами запросов в сутки. Количество параметров в конструкторе вас беспокоит? Ну а если вы тянете 20 полей из таблицы БД, какая вам разница? Билдер вам хоть чем то поможет? Не смешите. Зря потраченное время.

Ну. У нас есть JVM, нам нужна функциональщина... даже я, дотнетчик, сразу думаю о Scala.

А я нет. Для легковесной функциональщины достаточно того что есть в Java, плюс минус некоторые расшитрения, типа обсуждаемый. Для тяжелой артиллерии есть Кложур. Скала рядом не валялась.

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

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

Вот там где я могу, я ее легко и непринужденно применяю, и меня мое посредственное ее знание не сильно смущает. Насчет знания - я где-то в top 95% сдававших тест по ней на Linkedin. В итоге я оцениваю рынок скалистов как весьма странный и ненадежный, если можно так выразиться.

Вообще, я понимаю что для автора его собственное творение - самое лучшее :) Но реально не мешало бы сравнить хотя бы с парочкой других продуктов, которых мне кажется далеко не парочка, во-первых, потому что Java 8 уже 10 лет как вышла в релиз (а пререлизы были много раньше), и кто только не писал подобное за это время, и во-вторых, потому что потребности в этом достаточно очевидны.

Согласен, иначе получается "У нас есть 14 конкурирующих библиотек, давайте напишем одну им на замену". В результате очередная инхаус поделка которой пользуется полтора разработчика.

  1. Не хватает красивого примера боевого кода.

  2. Смотрю я на functional N и думаю, у вас голая java в проекте? Spring и mupstruct не используете? В дефолтный .map() стрима или опшионала передаешь бин с понятным методом мапинга...

Не вполне понимаю ваш вопрос. Этот данный проект полностью независим от каких либо иных библиотек и написан на ванильной Java. А дальше, можно использовать как угодно.

И да, любой конструктор с N параметрами эквивалентен FunctionN

Function<Integer,ArrayList> new_list = ArrayList::new;

Поясню.

Не хватает красивого примера прикладного, боевого, ентерпрайз-продакшн кода, где бы демонстрировалось преимущество использования данной библиотеки. Демонстрировалось бы как легко она помогает решить какую-то проблему при написании кода.

Потому что с моей колокольни, прикладного, боевого, ентерпрайз-продакшн разработчика, когда spring boot и mapstruct "стандарт" в отрасли - не видно кейсов применения данной библиотеки. Поэтому удивлённо спрашиваю, у вас в ваших проектах, голая java?

У меня проектов десятки. Где то используется спринг, где то нет. Где то есть Ломбок, где то нет. Давать пример продакшн кода невозможно по многим причинам. 1) Нельзя. 2) Он слишком большой для статьи. 3) Придется писать еще две стать с обоснованием, почему принятое решение оптимально. А иначе набегут спецы давать советы. Что собственно и происходит.

Как будто кто то с ними спорит, что есть много способов ободрать кролика.

Если же посмотреть на проблему сверху, то окажется, что наибольшую пользу функции начинают приносить в Дата Ориентированном подходе к разработке. В этом случае ОО превращается в узкое место. Уже не помогает, а мешает. Просто начинает тонуть в обилии классов делающих почти но не совсем то же самое. Уж не говоря про эффективность.

Перейти же целиком на функциональные рельсы мешают внешние обстоятельства и решения.

Sign up to leave a comment.

Articles