Functional JS patterns

배열의 첫번째 아이템을 반환한다.

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

2. tail

첫번째 아이템을 제외한 나머지 배열을 반환

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

다음 코드와 동일하다

const tail = ([x, ...xs]) => xs
Example
const array = [1,2,3,4,5]
tail(array) // [2,3,4,5]

3. Def

주어진 인자가 정의되어 있는지 반환한다.

const def = x => typeof x !== 'undefined'
Example
const defined = 'this is defined'
def(defined) // true
def(doesntExist) // false

4. Undef

주어진 인자가 정의되어 있지 않은지 반환한다.

const undef = x => !def(x)
Example
const defined = 'this is defined'
undef(defined) // false
undef(doesntExist) // true

5. Copy

배열을 복사한다

const copy = array => [...array]
Example
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]

6. Length

배열의 길이를 반환합니다. 배열 순회를 재귀를 통해 수행하는 전형적인 예입니다. 실제로는 array.length를 호출하시면됩니다.

const length = ([x, ...xs], len = 0) => def(x) ? length(xs, len + 1) : len
Example
const array = [1,2,3,4,5]
length(array) // 5

7. Reverse

역전된 배열을 반환합니다.

const reverse = ([x, ...xs]) => def(x) ? [...reverse(xs), x] : []
Example
const array = [1,2,3,4,5]
reverse(array) // [5,4,3,2,1]

Array.reverse()도 역전을 시킬수 있습니다만, 원래 배열의 데이터를 변경해버린다는 부작용이 있습니다.

8. First

배열의 최초 N개만큼 포함하는 새로운 배열을 반환합니다.

const first = ([x, ...xs], n = 1) => def(x) && n ? [x, ...first(xs, n - 1)] : []
Example
const array = [1,2,3,4,5]
first(array, 3) // [1,2,3]

9. Last

주어진 배열의 끝 n개를 포함하는 새로운 배열을 반환합니다.

const last = (xs, n = 1) => reverse(first(reverse(xs), n))
Example
const array = [1,2,3,4,5]
last(array, 3) // [3,4,5]

10. Slice

주어진 값을 특정 위치에 추가(Insert)한 새로운 배열을 반환한다

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)]
  : []
Example
const array = [1,2,4,5]
slice(array, 2, 99) // [1,2,99,4,5]

11. isArray

배열 여부를 반환한다. Array.isArray를 함수형으로 호출할수 있게 한다

const isArray = x => Array.isArray(x)
Example
const array = [1,2,3,4,5]
isArray(array) // true

12. Flatten

중첩된 배열을 단일 배열로 변환하여 반환한다.

const flatten = ([x, ...xs]) => def(x)
    ? isArray(x) ? [...flatten(x), ...flatten(xs)] : [x, ...flatten(xs)]
    : []
Example
const array1 = [1,2,3]
const array2 = [4,[5,[6]]]
flatten([array1, array2]) // [1,2,3,4,5,6]

13. Swap

배열의 두 값의 Swap된 새로운 배열을 반환한다

const swap = (a, i, j) => (
  map(a, (x,y) => {
    if(y === i) return a[j]
    if(y === j) return a[i]
    return x
  })
)
Example
const array = [1,2,3,4,5]
swap(array, 0, 4) // [5,2,3,4,1]

14. Map

주어진 배열의 값을 순회하여 주어진 함수에 넘기고, 함수의 반환값을 수집하여 배열로 반환한다. Array.map을 함수형으로 사용할수 있게 한다.

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)] : []
Example
const double = x => x * 2
map([1,2,3], double) // [2,4,6]

15. Filter

주어진 배열을 순회하며 주어진 함수에 넘기고, 함수에서 참이 반환된 값만 수집하여 배열로 반환한다. Array.filter를 함수형으로 사용할수 있게 한다.

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)]
    : []
Example
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]

16. Reject

Filter의 반대로 거짓이 반환된 값만 배열로 반환한다.

const reject = ([x, ...xs], fn) => {
  if (undef(x)) return []
  if (!fn(x)) {
    return [x, ...reject(xs, fn)]
  } else {
    return [...reject(xs, fn)]
  }
}
Example
const even = x => x % 2 === 0
const array = [1,2,3,4,5]

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

17. Partition

주어진 배열을 두개의 배열로 쪼갠다. 하나는 주어진 함수에 인자로 넘겨 참을 반환 받은 값의 배열, 다른 하나는 반대로 거짓을 반환받은 값의 배열이다.

const partition = (xs, fn) => [filter(xs, fn), reject(xs, fn)]
Example
const even = x => x % 2 === 0
const array = [0,1,2,3,4,5]

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

18. Reduce

배열이 주어졌을때 배열 값을 왼쪽에서 오른쪽 방향으로 순회하며 배열값과 accumulator를 함수에 인자로 넘겨 실행하고 최종적으로 단일 값을 반환한다 (배열이 아님)

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
Example
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]

19. ReduceRight

Reduce와 유사하나 배열을 오른쪽에서 왼쪽으로 순회한다.

const reduceRight = (xs, fn, memo) => reduce(reverse(xs), fn, memo)
Example
const flatten = (memo, x) => memo.concat(x)

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

20. Partial

함수와 인자 일부가 적용된 함수를 반환하여 나머지 인자를 추후에 넘겨받아 함수를 실행한다

const partial = (fn, ...args) => (...newArgs) => fn(...args, ...newArgs)
Example
const add = (x,y) => x + y
const add5to = partial(add, 5)

add5to(10) // 15

21. SpreadArg

배열을 받은 함수를 인자 목록을 받게 변환한다. Partial등에서 유용하게 사용할수 있다.

const spreadArg = (fn) => (...args) => fn(args)
Example
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

22. ReverseArg

인자 순서를 역전시킨다.

const reverseArgs = (fn) => (...args) => fn(...reverse(args))
Example
const divide = (x,y) => x / y
divide(100,10) // 10

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

인자의 순서를 변경하는 것은 인자 부분 적용(Partial)에서 유용할수 있다. 가끔 인자 부분 적용을 최초가 아니고 최후에 적용하고 싶을때도 있을 것이다.

const percentToDec = partial(reverseDivide, 100)

percentToDec(25) // 0.25

23. Pluck

배열에서 속성값을 추출한다. map과 같이 쓰면 유용하다

const pluck = (key, object) => object[key]
Example
const product = {price: 15}
pluck('price', product) // 15
Example
const getPrices = partial(pluck, 'price')

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

24. Flow

함수 목록이 주어지면, 각 함수가 이전 실행된 함수의 반환값을 인자로 받아 실행된다.

const flow = (...args) => init => reduce(args, (memo, fn) => fn(memo), init)
Example
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]

25. Compose

FLow와 유사하지만 실행순서가 꺼꾸로 수행된다. 좀더 자연스러운 함수 호출처럼 보일수 있다.

const compose = (...args) => flow(...reverse(args))
Example
const getFinalPrice = compose(tax, discount, getPrice)

// 함수 호출처럼 보인다: tax(discount(getPrice(x)))

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

26. Min

배열에서 최소 값을 반환한다. 빈 배열이 주어지면 Infinity를 반환한다.

const min = ([x, ...xs], result = Infinity) => def(x)
    ? x < result
        ? min(xs, x)
        : result
    : result
Example
const array = [0,1,2,3,4,5]

min(array) // 0

27. Max

배열에서 최대 값을 반환한다. 빈 배열이 주어지면 - Infinity가 반환된다.

const max = ([x, ...xs], result = -Infinity) => def(x)
    ? x > result
        ? max(xs, x)
        : max(xs, result)
    : result
Example
const array = [0,1,2,3,4,5]

max(array) // 5

28. Factorial

주어인 수에 해당하는 factorial 을 반환한다.

const factorial = (x, acum = 1) => x ? factorial(x - 1, x * acum) : acum
Example
factorial(5) // 120

29. Fibonacci

피보나치 수열을 반환한다.

const fib = x => x > 2 ? fib(x - 1) + fib(x - 2) : 1
Example
fib(15) // 610

30. 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)))
  ])
  : []
Example
const array = [8,2,6,4,1]

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

31. Reduce 화 하기

위에서 다룬 모든 함수들은 reduce 함수를 이용해서 구현할수 있다. reduce함수를 사용하면 대부분의 경우 더 나은 성능을 보여준다.

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))
Example
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
Tags: js  functional programming  FP