Чистые функции уменьшения — использование агрегатора в качестве состояния

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

Вот простой способ сохранить чистоту ваших функций reduce, используя агрегатор в качестве состояния.


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

let biggestNum = Number.NEGATIVE_INFINITY;

const sum = arr.reduce(
  (agg, val) => {
    biggestNum = Math.max(val, biggestNum);
    return agg + val;
  },
  0,
);

const sumAndBiggest = sum + biggestNum;
Войти в полноэкранный режим Выход из полноэкранного режима

В качестве альтернативы мы можем инкапсулировать всю связанную логику ВНУТРИ функции reduce:

const sumAndBiggest = arr.reduce(
  (agg, val, index, sourceArr) => {
    if (index === sourceArr.length - 1) {
      return agg.sum + val + agg.biggestNum;
    }

    return {
      sum: agg.sum + val,
      biggestNum: Math.max(val, agg.biggestNum),
    };
  },
  { sum: 0, biggestNum: Number.NEGATIVE_INFINITY },
);
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы заметите две вещи:

  • Мы используем агрегатор в качестве состояния — мы включаем фактическую сумму, которую мы уменьшаем, и самое большое число, которое мы нашли на данный момент.
  • Мы используем 3-й и 4-й аргументы функции reduce — текущий индекс и ссылку на массив, над которым мы итерируем, — чтобы узнать, является ли текущая итерация последней. Если да, то мы прибавляем наибольшее число и возвращаем конечную сумму вместо нового состояния.

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

  • Вы не получаете остатков переменных после завершения редукции. Когда кто-то читает код или отлаживает его, ему не придется беспокоиться о том, что эти переменные — biggestNum в нашем первом примере — используются где-то еще.
  • Ваша функция reduce — это чистая функция. Все, на что вам нужно обратить внимание, происходит внутри функции и ее аргументов. Это также означает упрощение отладки.
  • Все данные состояния собираются в мусор, когда они больше не используются.

К сожалению, TypeScript предлагает только возможность определить одно возвращаемое значение для reduce, и не позволяет нам различать конечное и промежуточные возвращаемые значения.
Это означает, что нам придется определить функцию reduce как способную возвращать оба типа, и использовать ключевое слово as для утверждения типа нашего агрегатора перед использованием:

interface IReduceState {
  sum: number,
  biggestNum: number;
}
const sumAndBiggest = arr.reduce<number | IReduceState>(
  (agg, val, index, sourceArr) => {
    const { sum, biggestNum } = agg as IReduceState
    if (index === sourceArr.length - 1) {
      return sum + val + biggestNum;
    }

    return {
      sum: sum + val,
      biggestNum: Math.max(val, biggestNum),
    };
  },
  { sum: 0, biggestNum: Number.NEGATIVE_INFINITY },
);
Вход в полноэкранный режим Выход из полноэкранного режима

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

Счастливого кодинга!


Спасибо, Йонатан Кра, за ваш любезный отзыв.

Оцените статью
Procodings.ru
Добавить комментарий