커링(Currying)은 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수들의 연속으로 변환하는 기법입니다. 수학자 하스켈 커리(Haskell Curry)의 이름에서 따온 용어입니다.
기본 개념
일반적인 함수:
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
커링된 함수:
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
curriedAdd(1)(2)(3); // 6
화살표 함수로 간단하게
const curriedAdd = a => b => c => a + b + c;
const result = curriedAdd(1)(2)(3); // 6
부분 적용 (Partial Application)
커링의 가장 큰 장점은 부분 적용이 가능하다는 것입니다:
const curriedAdd = a => b => c => a + b + c;
// 부분 적용
const add5 = curriedAdd(5);
const add5And3 = add5(3);
console.log(add5And3(2)); // 10 (5 + 3 + 2)
console.log(add5(4)(1)); // 10 (5 + 4 + 1)
실용적인 예제들
1. 문자열 검사 함수
const hasSubstring = substring => str => str.includes(substring);
const hasReact = hasSubstring('React');
const hasVue = hasSubstring('Vue');
console.log(hasReact('React 개발자')); // true
console.log(hasVue('Vue.js 프레임워크')); // true
// 배열에서 활용
const frameworks = ['React', 'Angular', 'Vue', 'Svelte'];
const reactItems = frameworks.filter(hasReact);
console.log(reactItems); // ['React']
2. 수학 연산
const multiply = a => b => a * b;
const divide = a => b => a / b;
const double = multiply(2);
const triple = multiply(3);
const half = divide(1/2); // 또는 multiply(0.5)
console.log(double(5)); // 10
console.log(triple(4)); // 12
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(double);
console.log(doubled); // [2, 4, 6, 8, 10]
3. 이벤트 핸들러
const log = level => message => {
console.log(`[${level}] ${message}`);
};
const logError = log('ERROR');
const logInfo = log('INFO');
logError('데이터베이스 연결 실패'); // [ERROR] 데이터베이스 연결 실패
logInfo('서버 시작됨'); // [INFO] 서버 시작됨
4. API 요청
const apiRequest = method => url => data => {
return fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: data ? JSON.stringify(data) : undefined
});
};
const get = apiRequest('GET');
const post = apiRequest('POST');
const put = apiRequest('PUT');
// 사용
get('/api/users')();
post('/api/users')({ name: '김철수', email: 'kim@example.com' });
자동 커링 함수 만들기
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
// 사용 예
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
라이브러리에서의 활용
Lodash
const _ = require('lodash');
const add = (a, b, c) => a + b + c;
const curriedAdd = _.curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
Ramda
const R = require('ramda');
const users = [
{ name: '김철수', age: 25 },
{ name: '이영희', age: 30 },
{ name: '박민수', age: 35 }
];
const getProperty = R.curry((prop, obj) => obj[prop]);
const getName = getProperty('name');
const names = users.map(getName);
console.log(names); // ['김철수', '이영희', '박민수']
커링의 장점
- 재사용성: 특정 매개변수가 고정된 새로운 함수를 쉽게 생성
- 가독성: 함수의 의도가 더 명확해짐
- 함수 조합: 다른 고차 함수들과 조합하기 쉬움
- 부분 적용: 필요할 때 일부 인자만 먼저 적용 가능
주의사항
- 성능: 함수 호출이 중첩되므로 약간의 오버헤드 발생
- 디버깅: 스택 트레이스가 복잡해질 수 있음
- 가독성: 과도하게 사용하면 오히려 코드를 이해하기 어려워질 수 있음
커링은 함수형 프로그래밍의 핵심 개념 중 하나로, 코드의 재사용성과 모듈성을 크게 향상시킬 수 있는 강력한 기법입니다.