본문 바로가기
JavaScript

[FP&ES6+] go, pipe, curry - (2)

by _sweep 2021. 12. 10.

함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.

 

 

curry

 

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

 

함수를 값으로 다루면서 받아둔 함수를 원하는 시점(= 원하는 개수의 인자가 들어왔을 때)에 평가시킨다.

 

함수(f)를 받아 함수 ((a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._))를 리턴한다.

리턴된 함수 ((a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._))가 실행되었을 때 인자가 2개 이상이라면 받아둔 함수를 즉시 실행한다.

인자가 2개 이상이 아니라면 함수 (..._) => f(a, ..._))를 리턴한 후에 그 이후에 인자가 들어오면 받은 인자를 합쳐 실행한다.

 

const mul = curry((a, b) => a * b);

console.log(mul);
console.log(mul(1));
console.log(mul(2)(4));

// output
// (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._)
// (..._) => f(a, ..._)
// 8

 

인자 2개를 받아 두 인자의 곱을 리턴하는 함수 mul을 curry 함수로 묶었다.

이때 curry 함수의 인자로 mul 함수가 들어간 것이기 때문에 mul을 출력해보면

(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._)

의 결과를 얻을 수 있다.

 

이후 mul에 1이라는 하나의 인자를 넣어주었다.

하지만 원하는 만큼의 인자가 들어오지 않았기에 이는 (..._) => f(a, ..._)를 리턴하며 다음 인자가 들어오길 기다린다.

 

마지막으로 mul에 2와 4의 인자를 넣어주었을 때 조건이 충족되고 그 값을 계산하여 8이라는 값을 리턴한다.

 

const mul3 = mul(3);
console.log(mul3(10));

// output
// 30

 

위와 같은 방식으로도 사용이 가능하다.

3을 곱하기 위해 mul3이라는 함수를 만들어 mul 함수의 인자값으로 3을 미리 넘겨주었다.

그리고 뒤이어 10이라는 인자값이 들어왔을 때 앞서 들어온 3과 뒤이어 들어온 10의 값을 넘겨 30이라는 값을 리턴한다.

 

 

✏️ 예제

 

curry를 이용하면 앞서 1편에서 만들었던 go 함수를 더 짧게 표현할 수 있다.

 

const fruits = [
    { name: '사과', price: 1000 },
    { name: '바나나', price: 2000 },
    { name: '딸기', price: 1500 },
    { name: '레몬', price: 1000 },
    { name: '복숭아', price: 3000 }
];

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const go = (...args) => reduce((a, f) => f(a), args);
const add = (a, b) => a + b;

const map = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(f(a));
  }
  return res;
});

const filter = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res;
});

const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
});

 

curry 함수를 이용하여 map, filter, reduce 함수를 각각 curry 함수를 이용해 묶었다.

이후 과일의 가격이 2000 미만인 과일들의 가격 총 합을 구한다고 한다면 다음과 같이 표현할 수 있다.

 

go(
  fruits,
  (fruits) => filter((f) => f.price < 2000)(fruits),
  (fruits) => map((f) => f.price)(fruits),
  (prices) => reduce(add)(prices),
  console.log,
);

// output
// 3500

 

이를 더 짧게 줄인다면 최종적으로 다음과 같이 표현할 수 있다.

 

go(
  fruits,
  filter((f) => f.price < 2000),
  map((f) => f.price),
  reduce(add),
  console.log,
);

// output
// 3500

 

map, filter, reduce에 curry 함수를 적용함으로써 map, filter, reduce 함수는 인자를 하나만 받으면 인자를 더 받기로 기다리는 함수를 리턴한다.

그래서 첫 번째 go 함수와 같이 인자 2개(함수와 fruits라는 iter)를 전달하여 계산이 이루어지도록 한 것이다.

그런데 이를 살펴보면 fruits를 받아 그대로 fruits를 전달한다.

여기서 평가되는 부분은 filter, map, reduce 함수 부분만 해당하기 때문에 2번째 go 함수와 같이 fruits가 생략이 가능해진 것이다.

 

더 쉽게 말하자면 go 함수의 정의를 살펴보면 된다.

go 함수는 go = (...args) => reduce((a, f) => f(a), args) 이다.

인자로 fruits가 들어오고 그 다음 첫 번째 함수로 filter가 들어왔을 때 a에는 fruits가, f에는 filter가 들어가게 된다.

즉, filter의 인자로 fruits가 들어가며 계산된 값이 그 다음의 map 함수로 들어가게 되고 이처럼 연속적으로 계산이 이루어지게 된다.

 

 

✏️ 예제(+pipe)

 

const sumPrice = pipe(
  map((p) => p.price),
  reduce(add),
);

go(
  products,
  filter((p) => p.price < 2000),
  sumPrice,
  console.log,
);

// output
// 3500

 

위 예제를 좀 더 응용하면 다음과 같이 쓸 수 있다.

위의 코드에서는 가격을 더하는 부분을 따로 추출해 pipe로 묶어 sumPrice라는 함수를 만들었다.

 

const baseSumPrice = (f) => pipe(filter(f), sumPrice);

go(
  products,
  baseSumPrice((p) => p.price < 2000),
  console.log,
);

// output
// 3500

 

그 다음으로는 값을 계산하는 부분을 pipe로 묶어 baseSumPrice라는 함수를 만들었다.

그리고 이 함수의 인자로 값의 범위를 지정하여 넘겨주면 baseSumPrice 함수에서 filter로 해당하는 값을 추출하고 sumPrice로 그 값이 넘어가 가격을 더하게 된다.

 

 

 

 

 

'JavaScript' 카테고리의 다른 글

[FP&ES6+] range & L.range  (0) 2021.12.22
[FP&ES6+] go, pipe, curry - (3)  (0) 2021.12.11
[FP&ES6+] go, pipe, curry - (1)  (0) 2021.12.10
[FP&ES6+] map, filter, reduce  (0) 2021.12.01
[FP&ES6+] 제너레이터  (0) 2021.12.01

댓글