Рубрики
Без рубрики

Функциональные JS с ES6 – рекурсивные шаблоны

Функциональное программирование было на подъеме и является темой, которая очень интересно для меня. Это позволяет мне писать церный, декларативный код, который легко проверить и разум.

Автор оригинала: Casey Morris.

Какая сделка здесь?

Функциональное программирование было на подъеме и является темой, которая очень интересно для меня. Это позволяет мне писать церный, декларативный код, который легко проверить и разум. Что такое функциональное программирование? Я отложу этот ответ кому-то с большим знанием на эту тему, Эрик Эллиот :

Функциональное программирование (часто сокращенные ФП) – это процесс строительства программного обеспечения, составив Чистые функции , избегая Общее состояние, Союзные данные и побочные эффекты Отказ Функциональное программирование – декларативный а не Императив и состояние приложения течет через чистые функции. Контраст с объектно-ориентированным программированием, где состояние приложения обычно передается и погружается с методами в объектах.

ES6 приносит многие функции, которые позволяют нам легко писать чистые функции, отдохнуть/распространяться одним из самых мощных. Использование Пословные параметры мы можем “цикл без петель” с Рекурсия Отказ В этой статье мы собираемся переписать многие широко используемые методы/функции JavaScript, которые позволяют функциональным шаблонам.

Предисловие

Следующие функции предназначены для демонстрации и обучения. Многие функции ниже представляют собой хвостовую рекурсию и должны быть оптимизированы дальше. Фиксация хвостовой рекурсии не является предметом этой статьи. ES6 приносит оптимизацию Call-Call, но должен использоваться в сочетании с «Используйте строгий» Отказ

Глава

Верните первый элемент в массиве. Полезно, когда вам нужно отделить первый элемент от остальных элементов массива. Для этого мы используем Разрушение задания Отказ

const head = ([x]) => x

Пример использования:

const array = [1,2,3,4,5]
head(array) // 1

Хвост

Верните все, кроме первого элемента в массиве.

const tail = ([, ...xs]) => xs

Что по сути так же, как написание:

const tail = ([x, ...xs]) => xs

Так как нам не нужно использовать х В возвращенном выходе мы можем бросить его, но держать запятую, чтобы получить остальные предметы в массиве.

Пример использования:

const array = [1,2,3,4,5]
tail(array) // [2,3,4,5]

Деф

Возврат, если аргумент определен.

const def = x => typeof x !== 'undefined'

Пример использования:

const defined = 'this is defined'
def(defined) // true
def(doesntExist) // false

Undef.

Возврат, если прилагаемый аргумент не определен.

const undef = x => !def(x)

Пример использования:

const defined = 'this is defined'
undef(defined) // false
undef(doesntExist) // true

Скопировать

Возвращает копию массива без использования Array.slice () Отказ Использует Распространение Отказ

const copy = array => [...array]

Пример использования:

let array = [1,2,3,4,5]
let copied = copy(array)
copied.push(6)

array // [1,2,3,4,5]
copied // [1,2,3,4,5,6]

Длина

Вернуть длину массива. Это очень простая форма цикла через массив с рекурсией, даже если значения массива не имеют значения в этом случае (увеличивает начиная с 1 для каждого элемента в массиве). Мы включаем Лен параметр, чтобы избежать хвост рекурсии Отказ

const length = ([x, ...xs], len = 0) => def(x) ? length(xs, len + 1) : len

Если мы не заботимся о рекурсии хвоста, мы можем написать это как:

const length = ([x, ...xs]) => def(x) ? 1 + length(xs) : 0

Это будет добавлять кадр стека для каждого элемента в массиве, тогда как версия, которая позволяет избежать рекурсионного хвоста, заменяет один кадр стека. Если массив прошел в большом достаточно большом, он будет бросать «максимальный размер стека вызовов».

Пример использования:

const array = [1,2,3,4,5]
length(array) // 5

Обеспечить регресс

Вернуть обратный массив.

const reverse = ([x, ...xs]) => def(x) ? [...reverse(xs), x] : []

Пример использования:

const array = [1,2,3,4,5]
reverse(array) // [5,4,3,2,1]

Array.Reverse () в порядке, но это изменяет значение на месте, которое является побочным эффектом. Рассмотрим следующее:

const array = [1,2,3,4,5]

const newArray = array.reverse() // [5,4,3,2,1]
array // [5,4,3,2,1]

// using the reverse method we just created
const array2 = [1,2,3,4,5]

const newArray2 = reverse(array2) // [5,4,3,2,1]
array2 // [1,2,3,4,5]

Первый

Возвращает новый массив, который содержит первые n элементов данного массива.

const first = ([x, ...xs], n = 1) => def(x) && n ? [x, ...first(xs, n - 1)] : []

Пример использования:

const array = [1,2,3,4,5]
first(array, 3) // [1,2,3]

Последний

Возвращает новый массив, который содержит последние n элементов данного массива.

const last = (xs, n = 1) => reverse(first(reverse(xs), n))

Пример использования:

const array = [1,2,3,4,5]
last(array, 3) // [3,4,5]

Ломтик

Возвращает новый массив со значением, вставленным в указанный индекс.

const slice = ([x, ...xs], i, y, curr = 0) => def(x)
  ? curr === i
    ? [y, x, ...slice(xs, i, y, curr + 1)]
    : [x, ...slice(xs, i, y, curr + 1)]
  : []

Пример использования:

const slice = ([x, ...xs], i, y, curr = 0) => def(x)
  ? curr === i
    ? [y, x, ...slice(xs, i, y, curr + 1)]
    : [x, ...slice(xs, i, y, curr + 1)]
  : []

isarray.

Возвращает, если прилагаемое значение является массивом. Позволяет нам написать Array.isarray () более функционально.

const isArray = x => Array.isArray(x)

Пример использования:

const array = [1,2,3,4,5]
isArray(array) // true

Сплющить

Сочетает в себе вложенные массивы в один массив.

const flatten = ([x, ...xs]) => def(x)
    ? isArray(x) ? [...flatten(x), ...flatten(xs)] : [x, ...flatten(xs)]
    : []

Пример использования:

const array1 = [1,2,3]
const array2 = [4,[5,[6]]]
flatten([array1, array2]) // [1,2,3,4,5,6]

Менять

Верните новый массив с 2 пунктами, поменяемыми на основе их индекса.

const swap = (a, i, j) => (
  map(a, (x,y) => {
    if(y === i) return a[j]
    if(y === j) return a[i]
    return x
  })
)

Пример использования:

const array = [1,2,3,4,5]
swap(array, 0, 4) // [5,2,3,4,1]

карта

От MDN: «… создает новый массив с результатами вызова предоставленной функции на каждом элементе в этом массиве».

const map = ([x, ...xs], fn) => {
  if (undef(x)) return []
  return [fn(x), ...map(xs, fn)]
}

Которые могут быть упрощены как:

const map = ([x, ...xs], fn) => def(x) ? [fn(x), ...map(xs, fn)] : []

Пример использования:

const double = x => x * 2
map([1,2,3], double) // [2,4,6]

Фильтр

От MDN: «… создает новый массив со всеми элементами, которые передают тест, реализованный предоставленной функцией».

const filter = ([x, ...xs], fn) => {
  if (undef(x)) return []
  if (fn(x)) {
    return [x, ...filter(xs, fn)]
  } else {
    return [...filter(xs, fn)]
  }
}

Которые могут быть упрощены как:

const filter = ([x, ...xs], fn) => def(x) 
    ? fn(x)
        ? [x, ...filter(xs, fn)] : [...filter(xs, fn)]
    : []

Пример использования:

const even = x => x % 2 === 0
const odd = x = !even(x)
const array = [1,2,3,4,5]

filter(array, even) // [2,4]
filter(array, odd) // [1,3,5]

Отклонять

Противоположность фильтра, возвращает массив, который не передает функцию фильтра.

const reject = ([x, ...xs], fn) => {
  if (undef(x)) return []
  if (!fn(x)) {
    return [x, ...reject(xs, fn)]
  } else {
    return [...reject(xs, fn)]
  }
}

Пример использования:

const even = x => x % 2 === 0
const array = [1,2,3,4,5]

reject(array, even) // [1,3,5]

Разбиение

Разбивает массив в две массивы. Тот, чьи элементы передают функцию фильтра и один элементы, отказоустойчивые.

const partition = (xs, fn) => [filter(xs, fn), reject(xs, fn)]

Пример использования:

const even = x => x % 2 === 0
const array = [0,1,2,3,4,5]

partition(array, even) // [[0,2,4], [1,3,5]]

Уменьшать

Из MDN: «… применяет функцию на фоне аккумулятора и каждый элемент в массиве (слева направо), чтобы уменьшить его до одного значения».

const reduce = ([x, ...xs], fn, memo, i) => {
  if (undef(x)) return memo
  return reduce(xs, fn, fn(memo, x, i), i + 1)
}

Которые могут быть упрощены как:

const reduce = ([x, ...xs], fn, memo, i = 0) => def(x)
    ? reduce(xs, fn, fn(memo, x, i), i + 1) : memo

Пример использования:

const sum = (memo, x) => memo + x
reduce([1,2,3], sum, 0) // 6

const flatten = (memo, x) => memo.concat(x)
reduce([4,5,6], flatten, [1,2,3]) // [1,2,3,4,5,6]

Редукторы

Похоже, что уменьшить, но применяет функцию из правого налево.

const reduceRight = (xs, fn, memo) => reduce(reverse(xs), fn, memo)

Пример использования:

const flatten = (memo, x) => memo.concat(x)

reduceRight([[0,1], [2,3], [4,5]], flatten, []) // [4, 5, 2, 3, 0, 1]

Частичный

Частично применять функцию путем заполнения любого количества его аргументов.

const partial = (fn, ...args) => (...newArgs) => fn(...args, ...newArgs)

Пример использования:

const add = (x,y) => x + y
const add5to = partial(add, 5)

add5to(10) // 15

Раздача

Конвертировать функцию, которая принимает массив на один, который принимает несколько аргументов. Это полезно при частично применении.

const spreadArg = (fn) => (...args) => fn(args)

Пример использования:

const add = ([x, ...xs]) => def(x) ? parseInt(x + add(xs)) : []
add([1,2,3,4,5]) // 15

const spreadAdd = spreadArg(add)
spreadAdd(1,2,3,4,5) // 15

Если вы хотите определить только одну функцию, вы можете написать ее как:

const add = spreadArg(([x, ...xs]) => def(x) ? parseInt(x + add(...xs)) : [])
add(1,2,3,4,5) // 15

В приведенном выше вам нужно помнить, чтобы распределить массив, который вы отправляете функцию рекурсивно, поскольку вы распространяете аргумент.

Reverseargs.

Реверсивный заказ на аргумент функции.

const reverseArgs = (fn) => (...args) => fn(...reverse(args))

Пример использования:

const divide = (x,y) => x / y
divide(100,10) // 10

const reverseDivide = reverseArgs(divide)
reverseDivide(100,10) // 0.1

Реверсивные аргументы могут быть полезны при частично применении аргументов. Иногда вы хотите частично применить аргументы в конце списка, а не те, которые в начале. Реверсирование аргументов позволяет нам это сделать.

const percentToDec = partial(reverseDivide, 100)
percentToDec(25) // 0.25

Вычеркивать

Извлечь значение свойства из массива. Полезно в сочетании с карта функция.

const pluck = (key, object) => object[key]

Пример использования:

const product = {price: 15}
pluck('price', product) // 15

const getPrices = partial(pluck, 'price')
const products = [
  {price: 10},
  {price: 5},
  {price: 1}
]
map(products, getPrices) // [10,5,1]

Поток

Каждая функция потребляет возвращаемое значение функции, которая пришла ранее.

const flow = (...args) => init => reduce(args, (memo, fn) => fn(memo), init)

Пример использования:

const getPrice = partial(pluck, 'price')
const discount = x => x * 0.9
const tax = x => x + (x * 0.075)
const getFinalPrice = flow(getPrice, discount, tax)

// looks like: tax(discount(getPrice(x)))
// -> get price
// -> apply discount
// -> apply taxes to discounted price

const products = [
  {price: 10},
  {price: 5},
  {price: 1}
]

map(products, getFinalPrice) // [9.675, 4.8375, 0.9675]

Составить

Так же, как поток, но аргументы применяются в обратном порядке. Составьте спички более естественным образом с использованием функций. Используя те же данные, как определено для поток Функция:

const compose = (...args) => flow(...reverse(args))

Пример использования:

const getFinalPrice = compose(tax, discount, getPrice)

// looks like: tax(discount(getPrice(x)))

map(products, getFinalPrice) // [9.675, 4.8375, 0.9675]

Мин

Верните наименьшее число в массиве. Возвращает Бесконечность Если Array поставляется пуст.

const min = ([x, ...xs], result = Infinity) => def(x)
    ? x < result
        ? min(xs, x)
        : result
    : result

Пример использования:

const array = [0,1,2,3,4,5]

min(array) // 0

Максимум

Верните наибольшее количество в массиве. Возвращает -Infinity Если Array поставляется пуст.

const max = ([x, ...xs], result = -Infinity) => def(x)
    ? x > result
        ? max(xs, x)
        : max(xs, result)
    : result

Пример использования:

const array = [0,1,2,3,4,5]

max(array) // 5

Факториал

Возвращает факториал числа. Использует аккумулятор, чтобы разрешить замену кадров стека, чтобы обеспечить возвращение больших факторов.

const factorial = (x, acum = 1) => x ? factorial(x - 1, x * acum) : acum

Пример использования:

factorial(5) // 120

Фибоначчи.

Возвращает номер Фибоначчи в данном месте.

const fib = x => x > 2 ? fib(x - 1) + fib(x - 2) : 1

Пример использования:

fib(15) // 610

QuickSort.

Сортировать массив от наименьшего до самого большого. Это делается путем повторного заказа массива, чтобы он содержит две подзарядки, один с меньшими значениями, другой с большим значениями. Вышеуказанные шаги рекурсивно применяются в каждом подпараситете до тех пор, пока не осталось никаких массивов, что сглаживается для возврата отсортированного массива.

const quicksort = (xs) => length(xs)
  ? flatten([
    quicksort(filter(tail(xs), x => x <= head(xs))),
    head(xs),
    quicksort(filter(tail(xs), x => x > head(xs)))
  ])
  : []

Это также может быть реализовано с использованием Раздел , но требуется переменное назначение.

const quicksort = (array) => {
  if (!length(array)) return []
  const [less, more] = partition(tail(array), x => x < head(array))
  return flatten([quicksort(less), head(array), quicksort(more)])
}

Пример использования:

const array = [8,2,6,4,1]

quicksort(array) // [1,2,4,6,8]

Все как сокращение

Многие из функций выше могут быть преобразованы в сокращения, которые следует больше повысить производительность, если не все случаи. Это также показывает гибкость Уменьшить функция.

const reduce = ([x, ...xs], f, memo, i = 0) => def(x)
    ? reduce(xs, f, f(memo, x, i), i + 1) : memo

const reverse = xs => reduce(xs, (memo, x) => [x, ...memo], [])

const length = xs => reduce(xs, (memo, x) => memo + 1, 0)

const map = (xs, fn) => reduce(xs, (memo, x) => [...memo, fn(x)], [])

const filter = (xs, fn) => reduce(xs, (memo, x) => fn(x)
    ? [...memo, x] : [...memo], [])

const reject = (xs, fn) => reduce(xs, (memo, x) => fn(x)
    ? [...memo] : [...memo, x], [])

const first = (xs, n) => reduce(xs, (memo, x, i) => i < n
    ? [...memo, x] : [...memo], [])

const last = (xs, n) => reduce(xs, (memo, x, i) => i >= (length(xs) - n)
    ? [...memo, x] : [...memo], [])

const merge = spreadArg(xs => reduce(xs, (memo, x) => [...memo, ...x], []))

const flatten = xs => reduce(xs, (memo, x) => x
    ? isArray(x) ? [...memo, ...flatten(x)] : [...memo, x] : [], [])

const add = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo + y, x))

const divide = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo / y, x))

const multiply = spreadArg(([x, ...xs]) => reduce(xs, (memo, y) => memo * y, x))

Пример использования:

reverse([1,2,3]) // [3,2,1]
length([1,2,3]) // 3
map([1,2,3], double) // [2,3,4]
filter([1,2,3,4], even) // [2,4]
reject([1,2,3,4], even) // [1,3]
first([1,2,3,4], 3) // [1,2,3]
last([1,2,3,4], 2) // [3,4]
merge([1,2,3],[4,5,6]) // [1,2,3,4,5,6]
flatten([1,[2,3,[4,[5,[[6]]]]]]) // [1,2,3,4,5,6]
add(1,2,3,4,5) // 15
multiply(2,5,10) // 100
divide(100,2,5) // 10

Обертывание

Я надеюсь, что эта статья помогает пролить представление о некоторых из моделей, доступных с JavaScript и ES6. Многие проблемы, которые могут быть решены с итерацией/петлями, также могут быть решены функционально с помощью рекурсии. Я надеюсь, что эта статья также смогла показать вам гибкость функции уменьшения.

Если вам нравится этот пост и нужна помощь с проблемой или искала нанимать фрилансера, я доступен для реагирования, Redux и JavaScript!