커링 (Currying)
자바스크립트(JavaScript)에서 커링(Currying)은 함수(Function)를 여러 단계로 나누어 호출할 수 있게 만드는 기법이다. 다중 매개변수(Parameter)를 받는 함수(Function)를 단일 매개변수(Parameter)를 받는 함수들의 연속으로 변환한다. 함수형 프로그래밍(Functional Programming)에서 자주 사용되며, 코드의 재사용성과 유연성을 높인다. 이 포스팅에서는 커링(Currying)의 정의, 구현 방법, 기존 함수와의 차이, 실무에서의 활용 사례를 다룬다.
커링(Currying)은 함수(Function)의 호출 방식을 바꿔, 부분 적용(Partial Application)을 쉽게 구현한다. 이를 통해 코드의 모듈화와 가독성을 개선하며, 복잡한 로직을 단순화할 수 있다. 자바스크립트(JavaScript)에서 커링(Currying)은 화살표 함수(Arrow Functions)와 결합하여 강력한 도구로 작용한다. 다양한 예제를 통해 커링(Currying)이 어떻게 동작하고, 실제 애플리케이션에서 어떤 가치를 제공하는지 알아본다.
커링(Currying)이란 무엇인가?
커링(Currying)은 다중 인자를 받는 함수(Function)를 단일 인자를 받는 함수들의 체인으로 변환하는 과정이다. 예를 들어, 두 개의 매개변수(Parameter)를 받는 함수(Function)를 커링(Currying)하면, 첫 번째 인자를 받은 후 두 번째 인자를 받는 함수를 반환한다. 이는 함수형 프로그래밍(Functional Programming)의 핵심 개념 중 하나로, 하스켈(Haskell) 언어의 수학자 해스켈 커리(Haskell Curry)에서 이름을 따왔다.
기본적인 커링(Currying) 예제는 다음과 같다:
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
function curriedAdd(a) {
return function(b) {
return a + b;
};
}
console.log(curriedAdd(2)(3)); // 5
위 코드에서 add
는 일반 함수(Function)로 두 인자를 한 번에 받는다. 반면 curriedAdd
는 커링(Currying)된 함수로, 첫 번째 인자 a
를 받은 후 두 번째 인자 b
를 받는 함수를 반환한다. 호출 방식이 add(2, 3)
에서 curriedAdd(2)(3)
으로 바뀌며, 이는 함수 호출을 단계적으로 처리할 수 있게 한다.
화살표 함수(Arrow Functions)를 사용하면 더 간결해진다:
const curriedAddArrow = a => b => a + b;
console.log(curriedAddArrow(2)(3)); // 5
커링(Currying)의 핵심은 함수(Function)가 모든 인자를 한 번에 받는 대신, 인자를 하나씩 받아 최종 결과를 도출할 때까지 함수를 반환하는 구조다. 이를 통해 특정 인자를 고정하고 나머지를 나중에 제공할 수 있다:
const addFive = curriedAddArrow(5);
console.log(addFive(3)); // 8
console.log(addFive(10)); // 15
위 예제에서 addFive
는 5
를 고정한 함수로, 이후 어떤 숫자를 넣든 5
를 더한 결과를 반환한다. 이는 커링(Currying)이 부분 적용(Partial Application)을 가능하게 하는 방식이다.
커링(Currying)은 자바스크립트(JavaScript)가 함수를 일급 객체(First-Class Citizen)로 다루는 특성을 활용한다. 함수가 값처럼 변수에 할당되고, 다른 함수의 인자나 반환값으로 사용될 수 있기 때문에, 커링(Currying)은 자연스럽게 구현된다. 자바스크립트(JavaScript)의 클로저(Closure)도 커링(Currying)에서 중요한 역할을 한다. 클로저(Closure)는 함수가 외부 변수에 접근할 수 있게 해, 커링(Currying)된 함수가 이전 인자를 기억하도록 만든다.
커링(Currying)의 구현 방법
커링(Currying)을 구현하는 방법에는 수동 구현과 유틸리티 함수를 사용하는 두 가지 접근이 있다. 각각의 방식을 예제와 함께 알아본다.
1. 수동 커링(Currying)
함수(Function)를 직접 커링(Currying)하여 단계별로 작성한다.
function multiply(a, b, c) {
return a * b * c;
}
function curriedMultiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
console.log(multiply(2, 3, 4)); // 24
console.log(curriedMultiply(2)(3)(4)); // 24
화살표 함수(Arrow Functions)로 더 간결하게:
const curriedMultiplyArrow = a => b => c => a * b * c;
console.log(curriedMultiplyArrow(2)(3)(4)); // 24
부분 적용(Partial Application)을 활용한 예:
const double = curriedMultiplyArrow(2);
const sixTimes = double(3);
console.log(sixTimes(4)); // 24
console.log(double(5)(3)); // 30
2. 유틸리티 함수로 커링(Currying)
일반 함수(Function)를 자동으로 커링(Currying)하는 헬퍼 함수를 만든다.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return function(...moreArgs) {
return curried(...args, ...moreArgs);
};
};
}
const addThree = (a, b, c) => a + b + c;
const curriedAddThree = curry(addThree);
console.log(curriedAddThree(1)(2)(3)); // 6
console.log(curriedAddThree(1, 2)(3)); // 6
console.log(curriedAddThree(1, 2, 3)); // 6
위 curry
함수는 원래 함수의 매개변수(Parameter) 수를 확인하고, 충분한 인자가 제공되면 결과를 계산하며, 그렇지 않으면 추가 인자를 기다리는 함수를 반환한다. 이 방식은 유연성을 제공한다.
더 복잡한 함수에 적용:
const formatMessage = (prefix, name, suffix) => `${prefix}, ${name}${suffix}`;
const curriedFormat = curry(formatMessage);
const helloFormat = curriedFormat("안녕");
const helloUser = helloFormat("철수");
console.log(helloUser("!")); // 안녕, 철수!
console.log(helloFormat("영희")("~")); // 안녕, 영희~
커링(Currying)과 일반 함수의 차이
커링(Currying)은 일반 함수와 호출 방식, 설계 철학에서 차이가 있다.
1. 호출 방식
- 일반 함수: 모든 인자를 한 번에 받는다.
function subtract(a, b) {
return a - b;
}
console.log(subtract(5, 3)); // 2
- 커링(Currying): 인자를 단계별로 받는다.
const curriedSubtract = a => b => a - b;
console.log(curriedSubtract(5)(3)); // 2
2. 부분 적용(Partial Application)
- 일반 함수: 별도 처리가 필요하다.
function multiplyBy(factor, number) {
return factor * number;
}
const triple = number => multiplyBy(3, number);
console.log(triple(4)); // 12
- 커링(Currying): 자연스럽게 지원한다.
const curriedMultiplyBy = factor => number => factor * number;
const tripleCurried = curriedMultiplyBy(3);
console.log(tripleCurried(4)); // 12
3. 함수 조합(Function Composition)
커링(Currying)은 함수 조합(Function Composition)과 잘 맞는다:
const compose = (f, g) => x => f(g(x));
const curriedAddOne = x => x + 1;
const curriedDouble = x => x * 2;
const addThenDouble = compose(curriedDouble, curriedAddOne);
console.log(addThenDouble(5)); // 12
실무 예제
커링(Currying)의 실무 활용 사례를 통해 그 유용성을 확인한다.
1. 로깅 함수
const log = level => message => console.log(`[${level}] ${message}`);
const infoLog = log("INFO");
const errorLog = log("ERROR");
infoLog("시스템 시작"); // [INFO] 시스템 시작
errorLog("서버 오류"); // [ERROR] 서버 오류
2. 단위 변환
const convert = factor => unit => value => `${value * factor} ${unit}`;
const kmToMiles = convert(0.621371);
const metersToFeet = convert(3.28084);
console.log(kmToMiles("miles")(5)); // 3.106855 miles
console.log(metersToFeet("feet")(10)); // 32.8084 feet
3. 데이터 필터링
const filterBy = key => value => obj => obj[key] === value;
const users = [
{ name: "철수", age: 25 },
{ name: "영희", age: 30 }
];
const filterByAge = filterBy("age");
const adults = users.filter(filterByAge(30));
console.log(adults); // [{ name: "영희", age: 30 }]
4. API 요청 설정
const fetchWithConfig = baseUrl => endpoint => async () => {
const response = await fetch(`${baseUrl}${endpoint}`);
return response.json();
};
const api = fetchWithConfig("https://api.example.com");
const getUsers = api("/users");
getUsers().then(data => console.log(data));
5. 이벤트 핸들러 생성
const createHandler = type => id => event => {
console.log(`${type} 이벤트 발생, ID: ${id}, 값: ${event.target.value}`);
};
const inputHandler = createHandler("input");
const handlerForId1 = inputHandler("id1");
document.querySelector("#id1").addEventListener("input", handlerForId1);
6. 문자열 포매팅
const formatString = prefix => suffix => text => `${prefix}${text}${suffix}`;
const boldFormat = formatString("")("");
console.log(boldFormat("강조")); // 강조
7. 계산기 함수
const calculate = operation => a => b => {
switch (operation) {
case "add": return a + b;
case "multiply": return a * b;
default: return 0;
}
};
const addCalc = calculate("add");
console.log(addCalc(5)(3)); // 8
성능과 한계
장점
- 재사용성: 부분 적용(Partial Application)으로 함수를 재사용한다.
- 모듈화: 복잡한 로직을 분리한다.
- 가독성: 단계적 호출로 의도를 명확히 한다.
한계
- 호출 복잡성: 다중 호출이 코드 흐름을 어렵게 할 수 있다.
- 메모리 사용: 클로저(Closure)로 인해 메모리 부담이 늘어날 수 있다.
간단한 작업에는 일반 함수, 복잡한 재사용 로직에는 커링(Currying)을 사용한다.
마무리
커링(Currying)은 자바스크립트(JavaScript)에서 함수(Function)를 단계적으로 처리하며, 재사용성과 유연성을 제공한다.