Библиотека потоков Javascript — реализация

#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 для хорошего компромисса использования потоков в процедурном контексте.