Функциональное программирование не ограничено reduce
, filter
и map
; это о функциях. Это означает, что нам не нужно полагаться на извращенные знания, такие как Array.from ({ length: x })
, где объект со свойством length
можно рассматривать как массив. Такое поведение вызывает недоумение для начинающих и накладные расходы для всех остальных. Думаю, вам понравится писать программы, которые более четко кодируют ваши намерения.
reduce
начинается с 1 или более значений и уменьшается до (обычно) одного значения. В этом случае вы действительно хотите реверс из reduce
(или fold
), здесь называемый unfold
. Разница в том, что мы начинаем с одного значения и расширяем или раскрываем его (обычно) несколько значений.
Начнем с упрощенного примера, alphabet
. Мы начинаем развертывание с начального значения 97
, код символа для буквы a
. Мы прекращаем разворачиваться, когда код символа превышает 122
, код символа для буквы z
.
const unfold = (f, initState) =>
f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
, () => []
, initState
)
const alphabet = () =>
unfold
( (next, done, char) =>
char > 122
? done ()
: next ( String.fromCharCode (char) // value to add to output
, char + 1 // next state
)
, 97 // initial state
)
console.log (alphabet ())
// [ a, b, c, ..., x, y, z ]
Выше мы используем одно целое число для нашего состояния, но для других развертываний может потребоваться более сложное представление. Ниже мы показываем классическую последовательность Фибоначчи, разворачивая составное начальное состояние [ n, a, b ]
, где n
- уменьшающийся счетчик, а a
и b
- числа, используемые для вычисления терминов последовательности. Это демонстрирует, что unfold
может использоваться с любым состоянием семени, даже массивами или объектами.
const fib = (n = 0) =>
unfold
( (next, done, [ n, a, b ]) =>
n < 0
? done ()
: next ( a // value to add to output
, [ n - 1, b, a + b ] // next state
)
, [ n, 0, 1 ] // initial state
)
console.log (fib (20))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765 ]
Теперь у нас есть уверенность, чтобы написать pagination
. Опять же, наше начальное состояние - составные данные [ page, count ]
, так как нам нужно отслеживать добавляемое page
и сколько страниц (count
) мы уже добавили.
Еще одним преимуществом этого подхода является то, что вы можете легко параметризовать такие вещи, как 10
или -5
или +1
, и есть разумная семантическая структура для их размещения.
const unfold = (f, initState) =>
f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
, () => []
, initState
)
const pagination = (totalPages, currentPage = 1) =>
unfold
( (next, done, [ page, count ]) =>
page > totalPages
? done ()
: count > 10
? done ()
: next (page, [ page + 1, count + 1 ])
, [ Math.max (1, currentPage - 5), 0 ]
)
console.log (pagination (40, 1))
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
console.log (pagination (40, 14))
// [ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]
console.log (pagination (40, 38))
// [ 33, 34, 35, 36, 37, 38, 39, 40 ]
console.log (pagination (40, 40))
// [ 35, 36, 37, 38, 39, 40 ]
Выше есть два условия, которые приводят к вызову done ()
. Мы можем свернуть их, используя ||
, и код выглядит немного лучше
const pagination = (totalPages, currentPage = 1) =>
unfold
( (next, done, [ page, count ]) =>
page > totalPages || count > 10
? done ()
: next (page, [ page + 1, count + 1 ])
, [ Math.max (1, currentPage - 5), 0 ]
)