커링 (Currying) 은 프로그래밍 중급 기술이다. 나도 아직 현업에서 제대로 구현해서 "와 너무 편하다, 좋다"라고 느낀 적은 없다. 하지만 쓸 수 있는 상황이 언젠가 오지 않을까 라는 생각만 가지고 배웠다.
Currying 은 1967년 Christopher Strachey 가 Haskell Brooks Curry의 이름에서 착안한 것이다. Currying은 여러 개의 인자를 가진 함수를 호출할 경우, 파라미터의 수보다 적은 수의 파라미터를 인자로 받으면 누락된 파라미터를 인자로 받는 기법이다.
또한 커링이라는 개념은 자바스크립트뿐 만 아니라 다른 언어에도 있다. 단지 구현 로직만 다를 뿐이다.
기술적으로 커링은 f(a, b, c)처럼 단일 호출로 처리하는 함수를 f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합되도록 변환하는 것이다. 각각의 인수로 호출되니 a, b, c의 완성된 형태에 영향받지 않고 원하는 형태로 인수를 다룰 수 있을 것이다.
그래서 이번 포스트의 제목이 "파라미터를 건드려보자. currying에 대해서"이다.
이제 코드로 살펴보자
다음과 같은 stocks 변수가 있다.
이름을 key로 현재가, 구매가를 가진 객체를 value로 가지고 있다.
let myStocks = {
google : { currentPrice : 300 ,purchasePrice : 100 },
apple : { currentPrice : 200 ,purchasePrice : 200 },
tesla : { currentPrice : 100 ,purchasePrice : 300 },
}
function getRealProfit(fee, name) {
return myStocks[name].currentPrice - myStocks[name].purchasePrice - fee
}
console.log( getRealProfit( 50, "google") ); // 150
보자. RealProfit 에는 현재가 - 구매가 - 수수료 가 붙는다. 근데 증권사별로 수수료가 다를 수 있다.
getRealProfit( 0, "google"); // A 증권사 수수료 : 0
getRealProfit( 10, "google"); // B 증권사 수수료 : 10
getRealProfit( 100, "google"); // C 증권사 수수료 : 100
계속 코드를 구현하다가 다시 B 증권사에서 apple의 실제 수익을 받아와보자.
잠깐만.. B 증권사 수수료가 얼마였더라..? 코드를 다시 가서 주석을 봐야 한다. 이건 누가 봐도 잘못됐다.
그러면 바꿔야 할 듯하다. B 증권사 수수료를 변수로 선언해놓으면 어떨까?
//Before
let appleProfit_B = getRealProfit( 10, "apple");
//After
let B_fee = 10;
let appleProfit_B = getRealProfit( B_fee, "apple");
한결 좋아진 거 같다. 근데 뭔가 아쉽다. 분리해보고 싶다.
이때 등판하는 게 curry라는 기법이다.
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));
}
}
}
}
이제 이용해보자
let myStocks = {
google : { currentPrice : 300 ,purchasePrice : 100 },
apple : { currentPrice : 200 ,purchasePrice : 200 },
tesla : { currentPrice : 100 ,purchasePrice : 300 },
}
function getRealProfit(fee, name) {
return myStocks[name].currentPrice - myStocks[name].purchasePrice - fee
}
let B_fee = 50;
console.log( getRealProfit( B_fee, "google") ); // 150
getRealProfitB = curry(getRealProfit)(B_fee)
console.log ( getRealProfitB("google") ); // 150
getRealProfitB() 함수를 만들어서 B_fee를 미리 준 상태로 만들었다, 그리고 이제 getRealProfitB()는 B_fee는 받을 필요 없이 name만 받으면 되는 것이다.
요약하자면 파라미터를 미리 준 상태의 함수를 만든 것이다.
getRealProfitA = curry(getRealProfit)(A_fee)
getRealProfitB = curry(getRealProfit)(B_fee)
getRealProfitC = curry(getRealProfit)(C_fee)
이러한 방식으로 각 증권사별 getRealProfit 함수를 만들 수 있다.
물론 다음과 같은 방식으로도 만들수 있다.
function getRealProfitA(name) {
return myStocks[name].currentPrice - myStocks[name].purchasePrice - A_fee
}
하지만 만약 getRealProfit 함수에 fee(수수료) 뿐만 아니라 tax(세금)도 붙게 되는 상황을 생각해보자. 편하게 tax는 5라고 하자.
function getRealProfitA_withTax(name) {
return myStocks[name].currentPrice - myStocks[name].purchasePrice -5 - A_fee;
}
그럼 모든 증권사별, 세금 여부에 따라 함수를 만들어야 하는가.. 이건 너무 불편하다. 그래서 위에서 설명한 curry기법이 필요한 것이다.
curry 기법이 적용된 경우, tax가 포함된 함수를 구현해보자.
function getRealProfit(fee, name) {
return myStocks[name].currentPrice - myStocks[name].purchasePrice -5 - fee
}
getRealProfitA = curry(getRealProfit)(A_fee)
getRealProfitB = curry(getRealProfit)(B_fee)
getRealProfitC = curry(getRealProfit)(C_fee)
다른 건 건드릴 필요 없이 getRealProfit 함수만 건드리는 경우 모두 적용되는 편안함이 생겼다.
어찌 보면 함수의 파라미터를 체이닝으로 풀어버리고, 함수를 상속해서 만드는 상황이 되어버렸다.
요약해보자면 Currying의 경우 파라미터의 순서는 매우 중요하다. 앞에 있는 파라미터일수록 변동가능성이 적고 뒤로 갈수록 높아진다. 따라서 실제 코딩을 할때도 이러한 점을 유의하면서 설계해야한다.
또한 Currying은 함수형 자바스크립트에서 매우 유용한 기술이다. 함수의 상속과 같은 형태로 함수 사이에 일관성이 생기고 수정하기 쉬운 코드가 된다. 따라서 적절하게 Currying 이용한 코딩을 해보자. 나름이 신세계가 열릴 것이다.
p.s 우리가 아주 유용하게 쓰는 loadsh에서 curry 함수를 제공한다. 내가 위해서 구현한 curry 함수보다 훨씬 유연한다. 추가자료에서 확인해보자.
<추가자료>
lodash.com/docs/4.17.15#curry
sujinlee.me/currying-in-functional-javascript/
ko.javascript.info/currying-partials
'개발 > 자바스크립트' 카테고리의 다른 글
[자바스크립트] 정규표현식(regex)에 대하여 (0) | 2021.03.01 |
---|---|
[자바스크립트] null 병합 연산자(nullish coalescing operator) ??에 대하여 (0) | 2021.02.28 |
[자바스크립트] 객체를 복사하는 다양한 방법에 대하여 (0) | 2021.02.22 |
[자바스크립트] primitive, 그 중에서도 null 에 대하여 (0) | 2021.02.17 |
[자바스크립트] 객체에 조건부로 속성을 추가하는 방법에 대해 (3) | 2021.02.15 |
댓글