본문 바로가기

고차 함수와 커링

함수를 인자와 반환값으로 다루는 고차 함수를 구현하고 여러 인자의 호출을 단항 함수 연쇄로 바꾸는 커링을 익힙니다.

함수형 프로그래밍의 핵심 특징 중 하나는 함수를 일급 객체(First-Class Citizen)로 다룬다는 점입니다.

즉 함수를 변수에 담고, 인자로 넘기고, 반환값으로 받을 수 있습니다.

이 특성을 바탕으로 고차 함수(HOF)와 커링(Currying)이라는 강력한 개념이 만들어집니다.

타입스크립트는 함수의 타입을 명확하게 정의할 수 있어, 고차 함수와 커링을 구현하고 이해하는 데 큰 도움을 줍니다.

고차 함수의 두 역할과 커링의 호출 계약을 구분한다 — 상태 추적

INFO·USER를 캡처한 함수 반환과 마지막 message 호출의 효과 발생이 다르다. 함수/캡처 값/출력을 고정 역할로 추적할 수 있다.

createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여준다.

HIGHER ORDER / CURRYING CONTRACT

커링된 logger는 단계마다 인자를 고정한다

고차 함수는 함수를 인자로 받거나 반환하며 두 역할을 함께 할 수도 있습니다. 교재 logger는 level과 tag를 앞 호출에서 고정하고, 마지막 message 호출에서 시간 조회·출력을 실행합니다.

커링된 logger는 단계마다 인자를 고정한다createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여준다.
createLogger('INFO')tag 함수를 반환
연결 관계createLogger('INFO') · tag 함수를 반환 → infoLogger('USER') · message 함수를 반환
infoLogger('USER')message 함수를 반환
연결 관계infoLogger('USER') · message 함수를 반환 → userLogger(message) · 시간 조회 · console 출력
userLogger(message)시간 조회 · console 출력

level → tag → message → void

logger의 세 단계 호출과 실행 시점createLogger INFO는 tag 함수를 반환하고 infoLogger USER는 message 함수를 반환한다. userLogger message 호출에서만 시간 조회와 console 출력이 실행되어 void가 된다. 앞 호출이 최종 로그를 실행한 것으로 해석하지 않는다.
createLogger('INFO')tag 함수를 반환
연결 관계createLogger('INFO') · tag 함수를 반환 → infoLogger('USER') · message 함수를 반환
infoLogger('USER')message 함수를 반환
연결 관계infoLogger('USER') · message 함수를 반환 → userLogger(message) · 시간 조회 · console 출력
userLogger(message)시간 조회 · console 출력

level → tag → message→ void

HIGHER ORDER

함수 입력·출력은 배타적이지 않다

applyTransformation은 callback을 받고 createFilterByMin은 함수를 반환합니다. 둘 다 고차 함수이며 하나의 함수가 두 역할을 함께 할 수도 있습니다.

CURRYING

단항 호출들의 연쇄

curriedAdd(1)(2)(3)은 각 호출마다 한 인자를 받습니다. 앞 인자를 고정해 add5, add5And10처럼 새 함수를 재사용합니다.

PARTIAL APPLICATION

부분 적용과 커링을 구분

부분 적용은 일부 인자를 고정하는 일반 개념입니다. 함수를 반환한다고 모두 커링이거나 컬렉션 연산인 것은 아닙니다.

EFFECTS

고차 함수도 효과를 낼 수 있다

logger의 앞 단계는 클로저 문맥을 만들고 마지막 단계는 new Date·console.log를 실행합니다. 고차 함수·커링이라는 분류만으로 순수성이 보장되지는 않습니다.

함수 타입에서 각 호출의 입력과 반환을 읽고, 캡처한 인자와 효과가 실행되는 시점을 확인합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 캡션createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여 준다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
INFO 캡처captured: INFO / effect: 아직 출력 없음createLogger(INFO)가 태그를 받을 함수를 반환한다부분 적용은 최종 로그 실행이 아니다
USER 캡처captured: INFO·USER / effect: 아직 출력 없음infoLogger(USER)가 메시지를 받을 함수를 반환한다두 번째 호출도 함수와 환경을 만든다
message 호출captured: INFO·USER / 반환: void마지막 호출에서 시간과 console 출력을 실행한다실제 효과는 message를 전달한 마지막 호출에서 발생한다

고차 함수의 두 역할과 커링의 호출 계약을 구분한다 — 상태 추적

INFO·USER를 캡처한 함수 반환과 마지막 message 호출의 효과 발생이 다르다. 함수/캡처 값/출력을 고정 역할로 추적할 수 있다.

createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여준다.

HIGHER ORDER / CURRYING CONTRACT

커링된 logger는 단계마다 인자를 고정한다

고차 함수는 함수를 인자로 받거나 반환하며 두 역할을 함께 할 수도 있습니다. 교재 logger는 level과 tag를 앞 호출에서 고정하고, 마지막 message 호출에서 시간 조회·출력을 실행합니다.

커링된 logger는 단계마다 인자를 고정한다createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여준다.
createLogger('INFO')tag 함수를 반환
연결 관계createLogger('INFO') · tag 함수를 반환 → infoLogger('USER') · message 함수를 반환
infoLogger('USER')message 함수를 반환
연결 관계infoLogger('USER') · message 함수를 반환 → userLogger(message) · 시간 조회 · console 출력
userLogger(message)시간 조회 · console 출력

level → tag → message → void

logger의 세 단계 호출과 실행 시점createLogger INFO는 tag 함수를 반환하고 infoLogger USER는 message 함수를 반환한다. userLogger message 호출에서만 시간 조회와 console 출력이 실행되어 void가 된다. 앞 호출이 최종 로그를 실행한 것으로 해석하지 않는다.
createLogger('INFO')tag 함수를 반환
연결 관계createLogger('INFO') · tag 함수를 반환 → infoLogger('USER') · message 함수를 반환
infoLogger('USER')message 함수를 반환
연결 관계infoLogger('USER') · message 함수를 반환 → userLogger(message) · 시간 조회 · console 출력
userLogger(message)시간 조회 · console 출력

level → tag → message→ void

HIGHER ORDER

함수 입력·출력은 배타적이지 않다

applyTransformation은 callback을 받고 createFilterByMin은 함수를 반환합니다. 둘 다 고차 함수이며 하나의 함수가 두 역할을 함께 할 수도 있습니다.

CURRYING

단항 호출들의 연쇄

curriedAdd(1)(2)(3)은 각 호출마다 한 인자를 받습니다. 앞 인자를 고정해 add5, add5And10처럼 새 함수를 재사용합니다.

PARTIAL APPLICATION

부분 적용과 커링을 구분

부분 적용은 일부 인자를 고정하는 일반 개념입니다. 함수를 반환한다고 모두 커링이거나 컬렉션 연산인 것은 아닙니다.

EFFECTS

고차 함수도 효과를 낼 수 있다

logger의 앞 단계는 클로저 문맥을 만들고 마지막 단계는 new Date·console.log를 실행합니다. 고차 함수·커링이라는 분류만으로 순수성이 보장되지는 않습니다.

함수 타입에서 각 호출의 입력과 반환을 읽고, 캡처한 인자와 효과가 실행되는 시점을 확인합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 캡션createLogger INFO가 tag 함수, USER 적용이 message 함수, 메시지 호출이 void 효과를 만드는 실제 3단계 호출을 보여 준다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
INFO 캡처captured: INFO / effect: 아직 출력 없음createLogger(INFO)가 태그를 받을 함수를 반환한다부분 적용은 최종 로그 실행이 아니다
USER 캡처captured: INFO·USER / effect: 아직 출력 없음infoLogger(USER)가 메시지를 받을 함수를 반환한다두 번째 호출도 함수와 환경을 만든다
message 호출captured: INFO·USER / 반환: void마지막 호출에서 시간과 console 출력을 실행한다실제 효과는 message를 전달한 마지막 호출에서 발생한다

고차 함수

정의: 고차 함수는 다음 중 하나 이상을 수행하는 함수입니다.

하나 이상의 함수를 인자로 받습니다.
함수를 결과로 반환합니다.

설명: 고차 함수는 함수를 데이터처럼 다룰 수 있게 해주어 코드의 추상화 수준을 높이고, 재사용성을 향상시키며, 중복을 줄이는 데 기여합니다.

자바스크립트/타입스크립트에서는 이미 많은 내장 고차 함수를 접했습니다.

예를 들어, 배열의 map, filter, reduce 메서드가 대표적인 고차 함수입니다.

이들은 콜백 함수(다른 함수)를 인자로 받아서 내부 로직을 수행합니다.

타입스크립트 적용 예시
함수를 인자로 받는 고차 함수
// 배열의 각 요소를 특정 함수에 따라 변환하는 고차 함수
function applyTransformation<T, U>(arr: T[], transformFn: (item: T) => U): U[] {
  return arr.map(transformFn); // map 자체가 고차 함수이지만, applyTransformation도 HOF
}

const numbers = [1, 2, 3, 4];

// 숫자를 두 배로 만드는 함수
const double = (n: number): number => n * 2;
const doubledNumbers = applyTransformation(numbers, double);
console.log("Doubled Numbers:", doubledNumbers); // [2, 4, 6, 8]

// 숫자를 문자열로 변환하는 함수
const toString = (n: number): string => `Number: ${n}`;
const stringNumbers = applyTransformation(numbers, toString);
console.log("String Numbers:", stringNumbers); // ["Number: 1", "Number: 2", "Number: 3", "Number: 4"]

// 타입스크립트는 transformFn의 타입과 반환 타입을 정확히 추론하고 검사합니다.

applyTransformation 함수는 transformFn이라는 또 다른 함수를 인자로 받습니다.

이를 통해 다양한 변환 로직을 유연하게 적용할 수 있습니다.

함수를 반환하는 고차 함수 (함수 팩토리)
// 특정 기준보다 큰 숫자를 필터링하는 함수를 반환하는 고차 함수
function createFilterByMin(minThreshold: number): (num: number) => boolean {
  return (num: number): boolean => num > minThreshold;
}

const data = [10, 25, 5, 40, 15];

// 20보다 큰 숫자를 필터링하는 함수 생성
const filterGreaterThan20 = createFilterByMin(20);
const filteredData1 = data.filter(filterGreaterThan20);
console.log("Greater than 20:", filteredData1); // [25, 40]

// 30보다 큰 숫자를 필터링하는 함수 생성
const filterGreaterThan30 = createFilterByMin(30);
const filteredData2 = data.filter(filterGreaterThan30);
console.log("Greater than 30:", filteredData2); // [40]

// 타입스크립트는 createFilterByMin의 반환 타입이 (num: number) => boolean 임을 명확히 합니다.

createFilterByMin 함수는 minThreshold 값을 클로저(closure)로 포획하여, 새로운 필터링 함수를 동적으로 생성하여 반환합니다.

이는 특정 조건을 미리 설정해둔 함수를 재사용할 때 유용합니다.

createFilterByMin(20) 자체는 배열을 필터링하지 않습니다. 이 호출은 20을 기억하는 (num: number) => boolean 함수를 만들고, 나중에 filter가 배열의 각 값으로 그 함수를 호출합니다. 25는 25 > 20을 만족하고 15는 만족하지 않으므로 결과가 [25, 40]입니다. 함수 생성 시점과 데이터 처리 시점을 나누어 읽습니다.

고차 함수의 이점
  • 코드 재사용성: 일반적인 로직을 고차 함수로 추상화하여 다양한 상황에서 재사용할 수 있습니다.
  • 유연성 및 확장성: 로직을 외부 함수로 분리함으로써, 기존 코드를 수정하지 않고 새로운 동작을 쉽게 추가하거나 변경할 수 있습니다 (OCP 준수).
  • 추상화: 복잡한 세부 구현을 숨기고, 코드의 의도를 더 명확하게 표현할 수 있습니다.
  • 결합도 감소: 특정 로직이 다른 코드와 강하게 묶이지 않고 독립적으로 존재할 수 있습니다.

커링

정의: 커링은 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수들의 체인으로 변환하는 함수형 프로그래밍 기법입니다.

설명: 커링된 함수를 호출하면 첫 번째 인자를 받고, 이어서 두 번째 인자를 받을 함수를 반환하고, 또 다음 인자를 받을 함수를 반환하는 식으로 동작합니다.

모든 인자가 채워지면 비로소 최종 결과가 반환됩니다.

기존 함수: f(a, b, c) 커링된 함수: f(a)(b)(c)

커링은 람다 대수(Lambda Calculus)에서 유래했으며, 특정 함수형 언어에서는 기본적으로 지원됩니다.

자바스크립트/타입스크립트에서는 직접 구현하거나 라이브러리(Ramda, Lodash/fp 등)의 도움을 받아 사용할 수 있습니다.

타입스크립트 구현 예시
// 일반적인 3개 인자를 받는 함수
function add(a: number, b: number, c: number): number {
  return a + b + c;
}
console.log("Normal add:", add(1, 2, 3)); // 6

// 커링된 add 함수 (직접 구현)
function curriedAdd(a: number): (b: number) => (c: number) => number {
  return (b: number) => {
    return (c: number) => {
      return a + b + c;
    };
  };
}

// 한 표현식에서 세 단계를 연속 호출
console.log("Curried add (all at once):", curriedAdd(1)(2)(3)); // 6

// 2. 부분 적용 (Partial Application) - 커링의 핵심 장점
const add5 = curriedAdd(5); // 첫 번째 인자 5를 고정하여 새로운 함수 생성
console.log("add5(1)(2):", add5(1)(2)); // 5 + 1 + 2 = 8

const add5And10 = add5(10); // 첫 번째, 두 번째 인자를 고정하여 또 다른 함수 생성
console.log("add5And10(3):", add5And10(3)); // 5 + 10 + 3 = 18

// 3. 타입스크립트의 타입 추론
// TypeScript는 curriedAdd 함수의 복잡한 타입을 정확히 추론합니다.
// (a: number) => (b: number) => (c: number) => number

curriedAdd 함수는 첫 번째 인자 a를 받은 후, b를 받을 함수를 반환하고, 이 함수가 b를 받은 후 c를 받을 함수를 반환합니다.

최종적으로 c를 받으면 모든 인자가 채워져 덧셈 결과가 반환됩니다.

커링의 이점
  • 부분 적용 (Partial Application): 커링은 함수에 인자를 부분적으로 적용하여 새로운 함수를 생성하는 것을 자연스럽게 만듭니다. 이는 특정 인자를 고정하여 재사용 가능한 특수화된 함수를 만들 때 매우 유용합니다.
  • 함수 조합 용이: 작은 단위의 커링된 함수들을 조합하여 더 복잡한 기능을 쉽게 구축할 수 있습니다. 각 함수의 입력과 출력이 명확해져 파이프라인 구성이 쉬워집니다.
  • 재사용성 증가: 미리 인자를 고정하여 새로운 함수를 생성할 수 있으므로, 코드의 재사용성이 높아집니다.
  • 가독성: 적절히 사용하면 함수 호출 체인이 데이터 흐름을 명확하게 보여주어 가독성을 높일 수 있습니다.
  • 지연 실행: 모든 인자가 채워질 때까지 실제 계산을 지연시킬 수 있습니다.
고차 함수와 커링의 관계

커링된 함수는 그 자체로 고차 함수의 한 형태입니다.

커링된 함수는 인자를 하나 받은 후 함수를 반환하기 때문입니다.

따라서 커링은 고차 함수를 활용하여 함수를 더 유연하고 재사용 가능하게 만드는 특정 패턴이라고 볼 수 있습니다.

실제 예시 (Logger with Currying)

커링된 로거는 로그 레벨, 태그, 메시지를 한 번에 받지 않고 호출 단계마다 하나씩 고정합니다.

앞에서 고정된 값은 클로저에 남아 다음 호출의 문맥이 되며, 마지막 메시지가 들어왔을 때 실제 로그가 만들어집니다.

type LogLevel = 'INFO' | 'WARN' | 'ERROR';

// 커링된 로거 생성 함수
function createLogger(level: LogLevel): (tag: string) => (message: string) => void {
  return (tag: string) => {
    return (message: string) => {
      const timestamp = new Date().toISOString();
      console.log(`[${timestamp}] [${level}] [${tag}] ${message}`);
    };
  };
}

// 특정 로그 레벨의 로거 함수 생성 (부분 적용)
const infoLogger = createLogger('INFO');
const errorLogger = createLogger('ERROR');

// 특정 태그의 로거 함수 생성 (또 다른 부분 적용)
const userLogger = infoLogger('USER');
const authLogger = errorLogger('AUTH');

// 메시지 로깅
userLogger('New user registered!');
userLogger('User login successful.');
authLogger('Failed login attempt for user: john_doe');
authLogger('Session expired for admin user.');

// 직접 커링된 함수 사용
createLogger('WARN')('NETWORK')('Network connection lost!');

이 예시에서 createLogger는 커링된 고차 함수입니다.

이를 통해 infoLogger, errorLogger, userLogger, authLogger와 같이 특정 목적에 맞게 인자가 부분적으로 적용된 특화된 로거 함수들을 쉽게 생성하고 재사용할 수 있습니다.

이는 로깅 시스템의 유연성과 구성 가능성을 크게 높여줍니다.


고차 함수와 커링은 함수를 인자로 전달하거나 단계적으로 인자를 고정해 재사용 단위를 만드는 방식입니다.

타입스크립트의 정적 타입 검사는 함수 합성 과정에서 인자 타입과 반환 타입의 불일치를 줄이는 데 사용됩니다.

고차 함수라는 분류가 순수성을 보장하지는 않습니다. 위 로거는 마지막 호출에서 시간 조회와 콘솔 출력이라는 효과를 실행합니다.

한 호출마다 남는 타입을 확인하기

원래 curriedAdd에서 const with4 = curriedAdd(4)와 const with4And7 = with4(7)을 만든다고 가정합니다. 각 변수는 숫자인가요, 함수인가요? with4And7(3)의 값과 with4(1)(2)의 값을 계산해 보세요. 이어 const audit = createLogger("WARN")("AUTH")만 만든 시점에 로그가 출력되는지, audit("retry")에서 어떤 레벨·태그를 쓰는지 설명하세요. 시각의 실제 문자열은 고정하지 않습니다.

부분 적용 후 타입과 실행 시점

with4는 (b: number) => (c: number) => number 함수이고 with4And7은 (c: number) => number 함수입니다. 결과는 각각 4 + 7 + 3 = 14와 4 + 1 + 2 = 7입니다. 마지막 인자까지 받기 전에는 덧셈 결과가 아니라 다음 함수를 얻습니다.

audit은 (message: string) => void 함수입니다. level과 tag를 고정한 앞 두 호출에서는 원래 코드의 new Date나 console.log에 아직 도달하지 않습니다. audit("retry")를 호출하면 그 시점의 시각과 WARN·AUTH·retry를 출력합니다. 고차 함수·커링이라는 형태가 이 마지막 호출의 부수 효과까지 없애지는 않습니다.

이 페이지 목차

이 페이지 목차