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

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

С точки зрения Java, подход во многом новаторский. Однако, есть ли смысл реализовывать на Java то, что хорошо ложится на языки функционального программирования?

Новаторский? Этому подходу в Java уже лет так… 10 пожалуй. Кому надо — те давно уже его использовали.

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

Проблематично, само собой. Более того, некоторые вещи вообще практически не сделать по разным причинам (спасибо type erasure, в частности). Но тем не менее, реализации были, некоторые вещи примерно 2008 года вполне себе гуглятся без проблем.

Ну вот скажем, “Lazy Error Handling in Java, Part 3: Throwing Away Throws” — это правда не совсем полноценная реализация, а скорее концепт.

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

Не всегда получается свободно выбирать язык, всё-же Java значительно шире распространён.

В Java давно были диаграммы — BPML.
Netbeans и Eclipce поддерживают редактирование BPML. Это правда enterprise, и BPML выполнял сервер, но некоторые библиотеки свободно генерили читаемую JAVA на выходе.
Также, очень многие реализовывали диаграммы во время бума workflow engines (Jira и др).
Но в те времена не был популярным термин функциональное программирование, диаграммы больше описывали DataFlow.
Java 8 — вполне себе функциональный язык, даже монады уже есть. Почему бы не реализовать ещё стрелки, хотя бы из интереса.

Это всё замечательно, конечно, но мой полусонный мозг не может понять — чем описанная конструкция принципиально отличается от Java Streams? Кроме того, что Streams уже есть в самом языке.

Стримы и стрелки — это два разных подхода к композиции программы из кусков. Из стрелок можно например построить обработку ошибок (Either<Result, Exception> это тоже стрелки, если что).


Т.е. я бы ответил на ваш вопрос так — они всем отличаются. То что вы увидели в примере нечто, показавшееся вам похожим на стримы — чистое совпадение, такой пример автор выбрал.

Не очень понял чем стрелка отличается от функции высшего порядка https://en.wikipedia.org/wiki/Higher-order_function. Есть какие-то концептуальные различия?
Стрелки — один из функциональных фреймворков, который определяет несколько функций (высшего порядка) для композиции других функций.
То есть это какбы библиотека, которая предлагает определённый подход к композиции функций.

Вот я всё равно не вижу отличия. Можно конкретно посмотреть на существующий интерфейс java.util.function.Function, который очень похож на вашу стрелку. В нём уже есть join (только называется по-другому — andThen).


А абстрактные пары и прочие туплы — это, конечно, ад. В джаве оно не нужно.

А давайте, расскажите, как вы скажем будете делать Map, у которого ключом является пара значений разных типов? Вместо tuple — отдельный класс поди заведете?

Естественно! Вы так говорите, будто класс — это что-то плохое.

Может, вы и анонимные классы по-прежнему предпочитаете лямбдам? )

Нет. При чём тут это? Несуразицу вы сейчас сказали.

При том же. Вы предпочитаете написать класс вместо Tuple.of(123, "string")? Даже тогда, когда этот класс не имеет никакого осмысленного имени (а анонимный вы в случае ключа для Map не сделаете), и не имеет никакого смысла вне контекста одной-двух строк кода?


Я именно об этом. Tuple — это ровно такой же безымянный, но с типизированными полями при этом, класс, для которого не имеет смысла придумывать названия, как лябмда — это безымянный класс с одним методом.


Это удобно. Если вы этого не чувствуете — это ваше дело.

Угу, а потом вы захотите этот тупл вернуть из метода и передать в другой метод, потом у вас тупл туплов образуется и понеслась. Никто уже не знает, что значит first, а что значит second. Если вам хочется производить write-only code — это, в принципе, тоже ваше дело.

Ха-ха )))


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


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


Вы никогда не знаете, что внутри у функции Function<Integer, String>, которую вам передали откуда-то, потому что это называется абстракция.


Возможно, это плохая абстракция. Но вы можете сделать более хорошую, например Function<Index, Address>. Все в ваших руках. И иногда Tuples для этого очень удобны.

andThen — это относится к монадам, а стрелки — более обобщенная концепция, в них ещё first есть.

А есть более реалистичный пример, чем вычисление простой математической формулы, требующее в 10 раз больше кода, чем надо (не считая подключения библиотеки)? Когда это может быть оправдано по сравнению с альтернативами уже имеющимися в языке?

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

Кстати решил переписать один из математических примеров в обычном стиле:


@FunctionalInterface
interface Function2 <A, B, R> { 
    public R apply (A a, B b);
}
...
Function2 <Double, Double, Double> sum_SinCos = (a, b) -> {
    double sin_res = Math.sin(a);
    double cos_res = Math.cos(b);

    return sin_res*sin_res + cos_res*cos_res;
};

Не сказал бы, что он в 10 раз короче, тем более тут мы лишились возможности повторного использования блоков.

Вы неправильно это делаете:


static double sumSinCos(double a, double b) {
    double sin_res = Math.sin(a);
    double cos_res = Math.cos(b);

    return sin_res*sin_res + cos_res*cos_res;
}

Если блоком вы считаете вызов метода Math.sin, то почему мы лишились возможности вызвать Math.sin в других местах?

Блок — это sin_cos (который определён как Action.of(Math::sin).combine(Math::cos)), который можно применять к паре значений.
В вашем примере sumSinCos — не лямбда, так что он уже не совсем эквивалентен: нужно ещё описать класс-обёртку. В итоге количество строк такое же.

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

А как, отвлекаясь от простых математических функций из примера, из статического метода сделать closure? Статический метод — это уже не эквивалент лямбды и примера со стрелкой.


Проблемы, решаемые данным подходом, показаны в начале статьи: явное описание dataflow в приложении. Если Ваc смущает, что простые математические функции можно вызвать проще, то можно написать более абстрактный пример:


Arrow<Pair<A, B>, С> someArrow = Action.of(f1).combine(f2)
        .join(arr2.combine(arr2))
        .join(f3);

someArrow.apply(Pair.of(instA, instB));

Тут f1, f2, arr2, f2 — какие-то существующие длинные функции, как это обычно и бывает. В безточечной записи с обычными вызовами это будет выглядеть примерно так (описание интерфейса лямбды опустим):


Function someLambda = (A instA, B instB) -> {
    return f3(arr2(f1(instA)), arr2(f2(instB)));
}

someLambda.apply(instA, instB);

Для простых функций оно выглядит почти одинаково, но что, если мы захотим использовать монады или сделать параллельный combine, например? Со стрелкой мы просто используем ParallelAction вместо Action, а вот лямбду придётся переписывать с futures со всеми вытекающими.

Во, теперь аргументация звучит убедительнее. Конечно, лямбда попроще будет выглядеть:


BiFunction<A, B, C> fn = (instA, instB) -> f3...;

И функциональный интерфейс объявлять, конечно, не нужно, всё есть в стандартной библиотеке. Но не суть. Автоматическое распараллеливание — окей, принимается. А что значит "захотим использовать монады"? Приведите пример.

Зато придётся описывать интерфейс, если там будет больше аргументов. Что мы пишем BiFunction, что Arrow — какой-то существенной разницы в объёме написанного не видно, честно говоря. Разве что более многословно описывается последовательность вызова функций, но и возможностей больше.


Работа с монадами в jArrows пока не реализована из коробки, в статье по ссылкам это называется Kleisli Arrow. С такой стрелкой можно делать композицию функций, которые возвращают монады.

Вы не замечаете тут простую разницу, что при помощи стрелок математические формулы строятся в том числе в runtime? Т.е., по сути, в случае формулы в коде, компилятор за вас построит что-то похожее на стрелки, с выводом типов и т.п.

Окей, добавив много кода, можно сэмулировать AST-дерево формул прямо из джава сорцов. При этом превращая все формулы в довольно-таки нечитаемое месиво. Но если это действительно нужно, почему бы не подключить символьный математический процессор, который будет в рантайме разбирать ваши формулы (и при необходимости делать с ними преобразования — дифференцировать или интегрировать символьно, например)? При этом формулы можно будет писать по-человечески, не теряя в читабельности. Каждой задаче свой инструмент.

Хм. Вообще-то Kleisli Arrows — это по большому счету и есть средство для написания такого "символьного математического процессора".

Напишите статью "как написать символьный математический процессор с помощью Kleisli Arrows". Интересно.

Похоже вы (точнее, Джон Хьюз) изобрели принцип Data Flow Programming.
В LabVIEW это работает примерно вот так:

как говорится, «как слышится, так и пишется».

Код с синусом и косинусом будет соответственно выглядеть ну как-то вот так, что ли:


Ну или ещё проще:

В своей области LabVIEW хорош, но в мейнстримовом прикладном программировании графический подход почему-то не получил распространения. А к области Java LabVIEW совсем слабо применим.
Да, согласен, вы правы, конечно. Просто ну очень уж напомнило. Кто знает, может через много-много (ну очень много) лет графическое функциональное программирование постепенно вытеснит императивное текстовое.
>Кто знает, может через много-много (ну очень много) лет графическое функциональное программирование постепенно вытеснит императивное текстовое.

Во многих чатах УЖЕ вытеснило. Смайлик смайлики погоняет…
Такой подход применим и для java — есть решение. Через пару недель опубликую статью — осталось хороший пример сделать.
Я имел ввиду, что на LabView очень редко пишут программы для энтерпрайза, в отличии от Java.
Конечно, это разные предметные области. Для энтерпрайза в монструозных проетах рисуют архитектуру в виде UML диаграмм. Из визуального программирования для java: BPMN2, ETL и enterprise integration patterns в eclipse редакторе для apache Camel
Есть ещё хороший пример — конструктор программ HiAsm
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории