JavaScript/함수형 프로그래밍과 JavaScript ES6+

go + curry를 사용하여 더 읽기 좋은 코드로 만들기

느리지만 꾸준하게 2021. 8. 19. 19:35

curry라는 함수 역시 함수를 값으로 다루면서 어떤함수를 원하는 시점에 평가시키는 함수이다.

curry라는 함수는 함수를 받아서 함수를 리턴하고 인자를 받아서 인자가 원하는 갯수만큼의 인자가 들어왔을 때 받아두었던 함수를 나중에 평가시키는 함수이다. 

 

첫번째 인자와 나머지 인자(a, ..._)를 받는데 만약에 인자가 두개이상 전달 되었을때라는 것을 뜻하는 것은 _에 length가 있을 때 일것이다.

 

length가 있다면 받아둔 함수를 즉시 실행하고( f(a, ..._) ) 만약아 아니라면 다시한번 함수를 리턴한다. 그리고 그 이후에 들어올 값들을 받아오고 함수를 실행하는 함수이다.

 

즉 미리받은 a와 새로받은 ..._인자를 전달하는 것이다.

 

정리하면 함수를 받아서 함수를 리턴하고 실행되었을 때 인자가 두개 이상이라면 받아둔 함수를 즉시 실행을 하고 인자가 두개보다 작다면 함수를 리턴한 후에 이후에 받은 인자들을 합쳐서 실행하는 함수이다.

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

아래와 같은 함수가 있다고 하자. 아래함수를 curry로 싸서 만들게 되면 함수를 전달해서 함수가 즉시 리턴이 된다.

mult를 확인해보면 아래와 같이 나타내진다.

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

    const mult = curry((a, b) => a * b);
    log(mult);

</script>

그리고 mult함수에 인자를 하나만 전달해도 역시 함수이다. 나머지 인자를 전달했을 때(..._) 받아두었던 함수에게 받아두었던 인자와f(a) 지금 받은f(..._)인자를 전달하도록 되어있다.

console.log(mult(1));

실행을 하면서 결과를 전달하면 아래와 같이 결과가 나온다.

console.log(mult(1)(2));

그래서 어떤함수를 만들어 놓고 아래와 같은 패턴으로 사용할 수 있다.

    const mult3 = mult(3);
    console.log(mult3(10));
    console.log(mult3(5));
    console.log(mult3(3));

또 go에서 사용했던 코드를

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

 

filter와 map과 reduce를 모드 curry를 적용해주면 즉 사용하고 있는 fx.js에서 map filter reduce에 각각 curry를 적용해주면 이 모든 함수들일 인자를 하나만 받으면 일단 이후인자들을 더 받기로 기다리는 함수를 리턴하도록 되어있다.

const log = console.log;

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

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;
});

 즉 아래와 같이 동작이 될 것이다. 

<script>

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

// 30000

 즉 products를 받아서 products에 전달하는 것은(products) products받는 filter(p => p.price < 20000)의 결과가 

그냥 filter(p => p.price < 20000)으로 해도 동작한다는 것이다. map(p => p.price)과 reduce(add)도 마찬가지로 아래와 같이 작성해도 동작한다.

<script>

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

// 30000

결론적으로 아래go코드를 더 쉬운 코드로 바꾼 것이다. 원래 맨처음 코드를 go함수를 통해서 순서를 반대로 뒤집고 currying을 통해서 간결한 표현을 만들었고 함수를 값으로 다루는 여러가지 함수들을 이용해서 깔끔한 코드를 만들었다.

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


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

//

<script>

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

// 30000

 

 

 

 

<출처 : 유인동 함수형 프로그래밍과 JavaScript ES6+>

https://www.inflearn.com/course/functional-es6/dashboard

 

함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의

ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,

www.inflearn.com