본문 바로가기

JavaScript/AboutJS

[JavsScript] 함수의 부분 적용과 유연한 합성 3: curry - 인자를 유연하게 다루는 고차 함수 만들기

https://daunje0.tistory.com/174

 

[JavsScript] 함수의 합성과 데이터 파이프라인 2: pipe - 재사용 가능한 함수 파이프라인 만들기

https://daunje0.tistory.com/173 [JavsScript] 함수의 합성과 데이터 파이프 라인 1. go함수를 값으로 다룰 수 있다는 자바스크립트의 특성 ( 일급함수 ) 때문에정말 다채로운 표현이 가능한 데, 그걸 배우는

daunje0.tistory.com

 

위 블로그에서 계속 됩니다.

 

  const log = console.log
  const products = [
    {name: '반팔티', price: 15000},
    {name: '긴팔티', price: 20000},
    {name: '핸드폰케이스', price: 15000},
    {name: '후드티', price: 30000},
    {name: '바지', price: 25000}
  ];

  const go = (...args) => args.reduce((a, f) => f(a));
  
  const add = (a,b) => a+ b

  log(
    products
      .filter(p => p.price < 20000)
      .map(p => p.price)
      .reduce(add)
  ) //30000

 

앞에서 다룬 go 함수를 활용하면 가독성 높은 map. filter, reduce를 만들 수 있습니다.

 

  go(
    products,
    products => products.filter(p => p.price < 20000),
    products => products.map(p => p.price),
    prices => prices.reduce(add),
    log
  ) // 30000

 

물론 이런 식의 사용도 가능합니다.

const log = console.log
const add = (a, b) => a + b;

const go = (...args) => args.reduce((a, f) => f(a));

const filter = f => products => products.filter(f);
const map = f => products => products.map(f);
const reduce = f => prices => prices.reduce(f);

  go(
    products,
    filter(p => p.price < 20000),
    map(p => p.price),
    reduce(add),
    log
  ) // 30000

 


 

 

다음 다룰 사용자 정의 함수 curry 는 이 글의 제목처럼 함수의 부분 적용과 유연한 합성이 특징입니다

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

  // 1. 인자로 함수를 받아 함수를 리턴한다
  // 2. 리턴된 함수가 실행 될 때, 인자가 2개 이상이면 받아 둔 f(a, ..._)함수를 즉시 실행
  //  (인자에서 전개연산자를 쓰면 배열화 되는 데, _의 길이가 1 이상이면 a와 함께 인자가 2개 이상이란 뜻이 된다)
  // 3. 1개만 인자가 있으면 , 새로 받은 인자 a 를 가지고 있다가 이후에 받은 ..._ 인자들과 함께 f(a, ..._) 를 실행한다
  // 4. 특정 값을 지닌 함수를 먼저 만들어 놓고 이후 실행시 값을 리턴하는 패턴

  // 예시
  const multiply = curry((a, b) => a * b)
  log('multiply::', multiply(1)(2)) // multiply:: 2

  const mult5 = multiply(5)
  log(mult5(1)) // 5
  log(mult5(2)) // 10
  log(mult5(3)) // 15

 


 

  go(
    products,
    products => products.filter(p => p.price < 20000),
    products => products.map(p => p.price),
    prices => prices.reduce(add),
    log
  ) // 30000

 

이 코드에 curry 를 적용한다면 아래와 같습니다

 

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

  go(
    products,
    filter(p => p.price < 20000),
    map(p => p.price),
    reduce(add),
    log
  );

 

 

그럼 기존 코드와 curry 적용한 코드의 차이점은 뭘까? 

 

  1. 유연성
    before: 각 함수가 정확히 두 개의 인자를 받도록 고정되어 있습니다. 
    after: 함수의 인자 개수에 상관없이 유연하게 사용할 수 있습니다. 

  2. 부분 적용
    before : 부분 적용이 가능하지만, 첫 번째 인자만 부분 적용할 수 있습니다.
    after : 어떤 인자든 부분 적용이 가능하며, 여러 단계로 나누어 적용할 수 있습니다.

  3. 재사용성
    before: 특정 상황에 최적화되어 있어 다른 상황에서 재사용하기 어려울 수 있습니다.
    after : 다양한 상황에서 더 쉽게 재사용할 수 있습니다. 함수들이 더 일반적이어서 다양한 데이터 구조에 사용할 수 있습니다.

  4. 가독성
    before : 간단하고 직관적일 수 있지만, 복잡한 로직에서는 중첩된 함수 호출로 인해 가독성이 떨어질 수 있습니다.
    after : 처음에는 복잡해 보일 수 있지만, 익숙해지면 더 간결하고 표현력이 풍부해집니다.

  5. 함수 합성
    before : 함수 합성이 제한적입니다.
    after : 함수들을 더 쉽게 조합하고 재사용할 수 있어 복잡한 로직을 구현하기 쉽습니다.


요약하자면 curry를 사용해야 하는 이유는 다음과 같습니다.

  • 더 큰 유연성: 함수를 다양한 방식으로 호출하고 조합할 수 있습니다.
  • 향상된 부분 적용: 어떤 인자든 부분적으로 적용할 수 있어 함수의 특수화가 쉽습니다.
  • 높은 재사용성: 더 일반적인 함수를 만들어 다양한 상황에서 사용할 수 있습니다.
  • 포인트-프리 스타일: 불필요한 인자 전달을 줄여 코드를 더 간결하게 만들 수 있습니다.