#javascript #functional-programming
#javascript #функциональное программирование
Вопрос:
потоки Java (или любая другая функциональная библиотека для других языков) очень хороши.
Например, у вас может быть ( js sudo code
).
Stream.of([1, 2, 3]).filter(x => x > 2).map(x => x * 5).result(); // [15]
Игнорируйте синтаксис или конкретную реализацию, это просто пример.
Теперь мои проблемы заключаются в том, что поток немного усложняется.
Например, если мне нужны разные данные на каждом шаге, подобном этому:
Stream.of([1,2, 3])
.map(x => x * 3)
.zip([4, 5, 6])
.map(..//here i need the initial array)
.map(..//here i need the zipped array)
.total(..//
Как вы видите, в некоторых методах мне нужно последнее вычисленное значение, в некоторых мне нужно начальное значение.
Кроме того, бывают ситуации, когда мне нужны промежуточные значения, но после того, как они будут вычислены.
map(x => x * 1).map(x => x * 2).map(x => x * 4).map(..//i need the result from 2nd map (x*2)
Это глупый пример, но он иллюстрирует проблему.
Есть ли хорошее решение этой проблемы.
Я думал, что смогу сохранить все данные в объекте, но это приводит к более подробному коду, потому что на каждом шаге я должен устанавливать и получать свойства из объекта.
Другой пример: Суммируем числа: [1, 2, 3, 4] -> 10
Фильтруем числа выше 2: [1, 2, 3, 4] -> [3, 4]
умножаем каждое число на сумму: [30, 40]
Stream.of([1,2,3, 4])
.sum()
.filter(// here will be the sum, but i want the initial array and later the sum)
.map(// here i want the filtered array and the calculated sum)
Спасибо
Комментарии:
1. Какая библиотека / язык предоставляет
zip
здесь? Это точно не используется по умолчанию в JDK. Кроме того, если вам нужен исходный массив, возможно, решите реализовать первыйmap
позже.2. Stream.js но проблема во всем. Если я хочу, например, отобразить исходный массив, затем заархивируйте результат в другой массив и сопоставьте результат с исходным массивом или промежуточным результатом. Это может быть массив, например, сумма элементов. Насколько я понимаю, на каждом шаге я теряю предыдущее значение, поэтому, если я хочу получить его позже, я могу это сделать. Спасибо
3. Я добавил пример того, чего я хочу достичь внизу
4. Это javascript, а не java?
5. Javascript — изменены теги и заголовок на javascript
Ответ №1:
Если вам нужен промежуточный результат, сохраните вычисления:
const initial = Stream.of([1,2,3,4]);
const total = initial.sum();
const result = initial.filter(x => x > 2).map(x => x * total);
Приведенный выше пример является наиболее логичным способом написания такого кода. Я не понимаю, почему вы хотели бы написать код, подобный:
Stream.of([1,2,3, 4])
.sum()
.filter(/* here will be the sum, but i want the initial array and later the sum */)
.map(/* here i want the filtered array and the calculated sum */)
Ваш пример сбивает с толку и вводит в заблуждение. Код, написанный в функциональном стиле, не нуждается в объединении в цепочку.
Комментарии:
1. Просто небольшой вопрос, поскольку я не знаком с API потоков JavaScript. В Java это не сработало бы, потому что вторая строка
total = initial.sum()
потребляла бы поток, а третья строка привела бы к исключению. Действительно ли этот код корректен в JavaScript? Поддерживает ли JavaScript многократное использование потоков?2. Это зависит от реализации потока. Если поток реализован чисто функциональным способом, как и должно быть в большинстве библиотек потоков, тогда он будет работать. Я обновлю свой ответ, чтобы показать, как это сделать.
3. Это не зависит от реализации. Это в документации : » Элементы потока посещаются только один раз в течение срока службы потока. Подобно итератору, должен быть сгенерирован новый поток для повторного использования тех же элементов исходного кода. » (Я предполагаю, что вы используете Java streams в своем ответе, но если у JavaScript есть сторонняя библиотека, которая работает подобным образом, было бы неплохо упомянуть об этом, поскольку, похоже, этого нет в стандартном JS.)
4. @DodgyCodeException Это потоки Java. Мы говорим о JavaScript, у которого нет встроенного интерфейса Stream. Следовательно, мы можем реализовать интерфейс Stream любым удобным для нас способом, включая чисто функциональный.
5. @DodgyCodeException Я думаю, что путаница заключается в том, что OP упоминает как потоки Java, так и потоки JavaScript в своем вопросе. Однако он явно хочет использовать потоки в JavaScript.
Ответ №2:
Я не знаю для всех языков — я в основном разработчик Java — но, в моем понимании, нет, нет серебряной пули, подобной той, которую вы ищете.
Метафора потока, используемая в библиотеках такого типа, всегда примерно такова
[input stream] --{read elements one by one}--> [calculate] --> [output stream]
Думайте о [input stream]
не как о представлении коллекции (хотя в 99% случаев это используется именно так). Рассмотрим это в более общем плане, например, как сетевой сокет, из которого вы считываете элементы один за другим. И после того, как вы прочитали элемент, он исчез из сетевого сокета, и вы не можете перемотать назад.
Итак, основной принцип заключается в том, что
- каждый элемент считывается ровно один раз,
- все
[calculate]
процессы потенциально могут выполняться параллельно, и [calculate]
процессы не имеют побочных эффектов
Этот принцип позволяет библиотеке оптимизировать и распараллеливать ваши вычисления внутри, что является основной целью Stream API.
Итак, чтобы ответить на ваш вопрос: если вам нужно вычисление, которое требует обращения к элементу потока более одного раза, вам нужно сохранить исходный поток или промежуточный результат в коллекции.
Комментарии:
1. Спасибо, я понимаю. Итак, процедурный код для приведенного выше примера выглядит следующим образом: пусть arr = [1, 2, 3, 4] пусть sum = arr.reduce((acc,x) => x acc, 0) пусть filtered = arr.filter(x => x > 2) пусть result = filtered.map(x => x * sum) Если я хочу достичь того же результата с помощью Stream, это будет что-то вроде этого? Stream.of({числа: [1, 2, 3, 4]}) . карта(o => {…o, sum: o.nums.reduce((acc, x) => acc x, 0)}) .карта(o => {…o, filtered: o.nums.filter(x => x > 2)}) .карта(o => o.filtered.map(x => x * o.sum))
2. Вероятно, вы могли бы написать это так, но, ИМХО, это было бы ужасным злоупотреблением концепцией. Просто потому, что у вас есть концепция (в данном случае потоки), это не значит, что вам нужно писать все с ее помощью. Я думаю, что мастерство в программировании заключается в использовании правильных инструментов, библиотек, API и, да, также стилей программирования или парадигм для решения поставленной задачи. Смотрите другой ответ Aadit для хорошего компромисса использования потоков в процедурном контексте.