본문 바로가기
클린코드 샘플(JavaScript)

함수형 프로그래밍, 상속 (커링, Currying)

by 찬찬2 2022. 11. 1.

Dirty Sample#1

const handler = (e, id) => {
    console.log(`${id} 여길 클릭했구나 ${e.offsetX}`);
}

document.addEventListener("click", (e) => handler(e, "front"));


↑↑ addEventListener의 2번째 인자는 콜백함수의 자리다. 보통 위와 같이 코드를 작성하는 경우가 많다.

Clean Sample#1

const handler = id => e => {
    console.log(`${id} 여길 클릭했구나 ${e.offsetX}`);
}

// 풀어쓰면 아래와 같다.
// const handler = (id) => {
//     return (e) => {
//         console.log(`${id} 여길 클릭했구나 ${e.offsetX}`);
//     }
// }

document.addEventListener("click", handler("front")); // 화살표 함수 생략


handler함수는 2개의 함수가 중첩되어 있는 형태이다. (함수를 반환하는 함수: currying, 익명함수(화살표 함수) + handler 함수 두개를 합친 형태의 함수이다.)
Dirty Sample#1에서 addEventListener의 두 번째 인자의 화살표함수를 생략할 수가 있어 가독성이 첫 번째 보다 비교적 높은 편이다.

currying 정의(위키피디아)
In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument. (아래 코드에서 a 다음 b, 그리고 b 다음 c의 인자를 차례대로 가져올 수 있다.)


const func = (a, b, c) => {
     console.log(`${a}, ${b}, ${c}`);
}

const func = (a) => (b) => (c) => {
     console.log(`${a}, ${b}, ${c}`);
}


- currying은 함수를 조합해 새로운 함수를 만들 수 있도록 도와준다


Currying Sample#1

const cache = (fn) => (a) => (b) => fn(a, b);
const add = (a, b) => a + b;

const result = cache(add)("hello")("world");

console.log(result); // helloworld

 

■ Currying Sample#2

function curry(f) { // 커링 변환을 하는 curry(f) 함수
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// usage
function sum(a, b) {
  return a + b;
}

let curriedSum = curry(sum);

alert( curriedSum(1)(2) ); // 3

 

■ Currying Sample#3 (advanced)

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);

alert( curriedSum(1, 2, 3) ); // 6, 보통때 처럼 단일 callable 형식으로 호출하기
alert( curriedSum(1)(2,3) ); // 6, 첫 번째 인수를 커링하기
alert( curriedSum(1)(2)(3) ); // 6, 모두 커링하기

**참고링크

객체지향 프로그래밍에서 "상속"의 개념과 같다. (화살표 함수로 간결하게 표현할 수 있다)


객체지향 프로그래밍의 장점 중 하나인 "상속"의 개념을 함수형 프로그래밍에서 사용할 수 있도록 처리해주는 것이 currying이다. 즉, curryign은 중복코드를 재활용해 사용할 수 있게 한다.

 

function 곱하기(a, b){
    return a * b;
}

const 곱하기X = x => a => {
    return 곱하기(a, x);
}

// 풀어쓰면 아래와 같다.
// function 곱하기X(x){
//     return function(a){
//         return 곱하기(a, x);
//     }
// }

const 곱하기2 = 곱하기X(2);
const 곱하기3 = 곱하기X(3);

console.log("곱하기2: ", 곱하기2(2)); // 4
console.log("곱하기3: ", 곱하기3(2)); // 6


곱하기X 함수는 배수가될 인자 x를 받는 재활용 가능한 함수이다.

"2x * 3 + 4 = ??"를 currying을 활용해 풀어보자.

 

// 2x * 3 + 4

const 곱하기 = (a, b) => a * b;
const 곱하기X = x => a => 곱하기(a, x);
const 곱하기2 = 곱하기X(2);
const 곱하기3 = 곱하기X(3);

const 더하기 = (a, b) =>  a + b;
const 더하기X = x => a => 더하기(x, a);
const 더하기4 = 더하기X(4);

const fomula = (x) => {
    return 더하기X(4)(곱하기3(곱하기2(x))); // 함수의 실행방향이 오른쪽에서 왼쪽이다.
}

const result = fomula(10);
console.log(`result: ${result}`); // 64


보다시피 괄호가 너무 많아 가독성이 매우 나쁘다. 그리고 formula 함수의 실행순서(방향)를 보면
"곱하기2 → 곱하기3 → 더하기4"이며 실행방향은 오른쪽에서 왼쪽이다. 보통 사람이 인지하는 순서는 왼쪽에서 오른쪽이다. 그렇기 때문에 코드를 해석하는데 다소 시간이걸리고 혼동될 수 있는 단점이 있다.

■ 해결방법: 배열 API "reduce"를 사용해 실행할 함수들을 조합하는 것이다.

function compose(){
	return [fn1, fn2, fn3, ...].reduce(); // enhancedFn 함수들의 조함으로 새로운 함수를 만들었다.
}


위와 같이 함수배열을 원하는 계산 함수(enhancedFn)로 변환하는 과정을 구현하면된다.

// 2x * 3 + 4

const 곱하기 = (a, b) => a * b;
const 곱하기X = x => a => 곱하기(a, x);
const 곱하기2 = 곱하기X(2);
const 곱하기3 = 곱하기X(3);

const 더하기 = (a, b) =>  a + b;
const 더하기X = x => a => 더하기(x, a);
const 더하기4 = 더하기X(4);

const fomula = [
    곱하기2,    // or 곱하기X(2)
    곱하기3,    // or 곱하기X(3)
    더하기4     // or 더하기X(4)
].reduce((prevFunc, nextFunc) => {
    return (x) => nextFunc(prevFunc(x)); // 연산
}, init => init); // 최종 결과물 데이터 형태를 reducer 두번째 인자로 넣는다. (입력값을 그대로 반환하는 함수형태)

const result = fomula(10);
console.log(`result: ${result}`); // 64


Reduce의 흐름


■ Reduce를 활용한 cleaner 코드

// 2x * 3 + 4

const 곱하기 = (a, b) => a * b;
const 곱하기X = x => a => 곱하기(a, x);
const 곱하기2 = 곱하기X(2);
const 곱하기3 = 곱하기X(3);

const 더하기 = (a, b) =>  a + b;
const 더하기X = x => a => 더하기(x, a);
const 더하기4 = 더하기X(4);

const compose = (...args) => {
    return args.reduce((prevFunc, nextFunc) => {
        return (...values) => {
            return nextFunc(prevFunc(...values));
        }
    }, init => init);
}

const fomula = compose(
    곱하기2,
    곱하기3,
    더하기4
);

const result = fomula(10);
console.log(`result: ${result}`);

댓글