Currying
은 다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것을 말한다.
예를 들어Currying
은 foo(a, b, c)
와 같은 단일 호출로 처리하는 함수를 foo(a)(b)(c)
처럼 사용하는 방식이다.
즉, 중첩된 Currying 함수로 각각의 인수를 하나씩 전달받고 필요한 인자를 모두 얻으면 원래의 함수(foo)를 실행하는 것이다.
아래 예제를 보자.
const curry = function (fn) {
return function (message) {
return function (name) {
return fn(message, name);
}
}
};
function greet(message, name) {
return `${name} 님, ${message}`;
}
// 일반 함수 호출
let result = greet('안녕하세요.', '철수');
console.log(result); // 철수 님, 안녕하세요.
// currying
const curryGreet = curry(greet);
result = curryGreet('누구세요.')('미애');
console.log(result); // 미애 님, 누구세요.
currying
은 원래의 함수(fn
)를 전달받고 나서, 내부적으로 중첩된 두 개의 함수를 선언하였다.
중첩된 함수 선언을 통해서 필요한 인자인 fn
, message
, name
을 모두 전달받는데, 각각의 인자는 렉시컬 스코프
를 가지기 때문에 클로저 함수에서 모두 참조가 가능하다.
결과적으로 원래의 함수인fn
는 전달받은 각각의 인자인 message
, name
로 실행되는데, 함수는 호출할 때마다 필요한 인자를 넘겨준다.
그럼, 함수 인자를 함수 호출로 나눠서 전달함으로써 얻게 되는 이점은 무엇일까?
Currying
의 가장 큰 장점은 재사용성
이다.
뿐만 아니라, Currying
은 함수의 인자를 미리 설정해 다른 역할을 하는 새로운 함수를 만든다.
즉, Currying
을 통해 생성된 새로운 함수가 기존보다 한정된 기능만 수행하도록 행동을 구체화한다.
위 예제를 다시 살펴보자.
// 일반 호출
let foo = greet('안녕하세요.', '철수');
let bar = greet('안녕하세요.', '미애');
let baz = greet('안녕하세요.', '짱구');
console.log(foo, bar, baz);
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.
// currying
const curryGreet = curry(greet);
const greets = curryGreet('안녕하세요.');
foo = greets('철수');
bar = greets('미애');
baz = greets('짱구');
console.log(foo, bar, baz);
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.
'안녕하세요.'
문구는 렉시컬 스코프
를 가지는 curryGreet()
함수의 message
에 미리 할당하기 때문에 불필요한 중복을 제거할 수 있다.
따라서,'안녕하세요.'
문구를 완성해주는 greets()
함수의 재사용성이 높아지고, 코드가 보다 간결해지는 것을 알 수 있다.
또한 curryGreet('안녕하세요.')
함수 호출을 통해, message
인자를 '안녕하세요.'
로 한정한 새로운 함수인 greets()
를 반환한다.
결과적으로, 기존의 greet(message, name)
함수보다 구체적인 행동을 수행하는 greets(name)
함수를 만들 수 있는 것이다.
명시적인 This Binding
을 위해 사용하는 Javascript 내장 함수인 bind()
도 실은 currying
작업을 하는 녀석 중 하나이다.
const greets = greet.bind(Object.create(null), '안녕하세요.');
foo = greets('철수');
bar = greets('미애');
baz = greets('짱구');
console.log(foo, bar, baz);
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.
그럼 이제 앞선 예제를 일반화하여 범용적으로 사용 가능하도록 curry
함수를 변형해보자.
const curry = (fn) => {
return function curryFn(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return (...factors) => curryFn(...args, ...factors);
}
}
};
function greet(message, name) {
return `${name} 님, ${message}`;
}
// 일반 함수 호출
let result = greet('안녕하세요.', '철수');
console.log(result); // 철수 님, 안녕하세요.
// currying
const curryGreet = curry(greet);
result = curryGreet('누구세요.')('미애');
console.log(result); // 미애 님, 누구세요.
원래의 함수가 요구하는 인자의 수(Function.length)가 모두 전달될 때까지 재귀적으로 curry 함수를 생성한다. 그리고 함수 호출에 필요한 인자가 모두 전달되면 curry 함수를 만들지 않고 원래의 함수를 호출한다.
curry
함수의 동작 방식을 살펴보면 다음과 같다.
누적된 인자 수(args.length
)가 fn
함수의 인자 수(fn.length
)보다 같거나 많은 때까지, 전달받은 인자(factors
)를 args
배열에 하나씩 추가해 curryFn
함수를 호출한다. 즉, 누적된 인자 수(args.length
)가 함수 인자의 수(fn.length
)보다 같거나 많을 때까지, curryFn
함수는 매번 인자를 하나씩 전달받아서 호출된다.
마침내 인자 수가 같아진다면, 누적된 인자(args)를 이용해 원래의 fn
함수를 호출한다.