고차 함수
higher-order function, higher order function, 고차함수, HOF, higher-order functions
정의
고차 함수 (Higher-Order Function, HOF): 다음 조건 중 하나 이상을 만족하는 함수.
- 함수를 인자로 받는다.
- 함수를 반환한다.
일급 함수 특성이 있어야만 가능한 패턴이다. 함수를 값처럼 다룰 수 있기 때문에 함수를 조립하거나 추상화하는 강력한 도구가 된다.
| 조건 | 예 |
|---|---|
| 함수를 인자로 받는다 | Array.prototype.map(fn) |
| 함수를 반환한다 | const multiply = x => y => x * y |
| 둘 다 | pipe(f, g) |
사용 상황
- 배열 변환:
map,filter,reduce - 함수 합성 (function composition): 여러 함수를 조립해 새 함수 생성
- 커링 (currying): 다중 인자 함수를 단일 인자 함수 체인으로 분해
- 데코레이터 패턴: 기존 함수에 캐싱, 로깅, 재시도 등을 덧씌움
- 추상화: 반복 로직을 HOF 로 분리해 DRY 원칙 실현
시각화
HOF 가 함수를 받아 새 함수 또는 결과를 내보내는 구조:
flowchart LR
subgraph HOF["고차 함수 (Higher-Order Function)"]
direction TB
IN["함수 fn (인자)"] --> BODY["HOF 본체 (로직)"]
BODY --> OUT["결과: 새 배열 / 새 함수"]
end
FN["x => x * 2"] --> IN
OUT --> RES["변환된 값 또는 함수"]
함수 합성 (pipe) 흐름: 입력이 각 함수를 차례로 통과한다.
flowchart LR
INPUT["입력"] --> F["trim(x)"]
F --> G["toLower(x)"]
G --> H["split(x)"]
H --> OUTPUT["결과"]
style F fill:#4a9eff,color:#fff
style G fill:#4a9eff,color:#fff
style H fill:#4a9eff,color:#fff
기본 사용법
map, filter, reduce
JavaScript 내장 배열 메서드가 가장 흔한 HOF 예시다.
const numbers = [1, 2, 3, 4, 5];
// map: 각 원소를 변환한 새 배열 반환
const doubled = numbers.map(x => x * 2);
// [2, 4, 6, 8, 10]
// filter: 조건에 맞는 원소만 새 배열로
const evens = numbers.filter(x => x % 2 === 0);
// [2, 4]
// reduce: 배열을 하나의 값으로 누적
const sum = numbers.reduce((acc, x) => acc + x, 0);
// 15
// 체이닝: filter -> map -> reduce
const result = numbers
.filter(x => x % 2 !== 0) // [1, 3, 5]
.map(x => x ** 2) // [1, 9, 25]
.reduce((a, b) => a + b, 0); // 35
함수를 반환하는 패턴
// multiplier 는 숫자를 받아 함수를 반환하는 HOF
function multiplier(factor) {
return (num) => num * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 배열 메서드와 함께
const numbers = [1, 2, 3];
console.log(numbers.map(double)); // [2, 4, 6]
console.log(numbers.map(triple)); // [3, 6, 9]
실전 예시
함수 합성: compose / pipe
// compose: 오른쪽 -> 왼쪽 순서로 적용
const compose = (...fns) => x =>
fns.reduceRight((v, f) => f(v), x);
// pipe: 왼쪽 -> 오른쪽 순서로 적용 (compose 반대)
const pipe = (...fns) => x =>
fns.reduce((v, f) => f(v), x);
const trim = str => str.trim();
const toLower = str => str.toLowerCase();
const split = str => str.split(' ');
const process = pipe(trim, toLower, split);
process(' Hello World ');
// ['hello', 'world']
커링 (Currying)
// 2인자 함수를 커리드 버전으로
const add = a => b => a + b;
const add5 = add(5);
add5(3); // 8
add5(10); // 15
// 실용 예: 로거 생성기
const withPrefix = prefix => message =>
`[${prefix}] ${message}`;
const logError = withPrefix('ERROR');
const logInfo = withPrefix('INFO');
logError('서버 연결 실패'); // [ERROR] 서버 연결 실패
logInfo('서버 시작됨'); // [INFO] 서버 시작됨
메모이제이션 데코레이터
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
function slowFib(n) {
if (n <= 1) return n;
return slowFib(n - 1) + slowFib(n - 2);
}
const fib = memoize(slowFib);
fib(40); // 빠름 (중복 계산 없음)
fib(40); // 즉시 반환 (캐시 히트)
재시도 HOF
function withRetry(fn, retries = 3, backoff = 300) {
return async function (...args) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await fn(...args);
} catch (err) {
if (attempt === retries - 1) throw err;
const delay = backoff * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
console.warn(`재시도 ${attempt + 1}/${retries}`);
}
}
};
}
const fetchWithRetry = withRetry(fetch, 3);
const res = await fetchWithRetry('/api/data');
reduce 로 pipeline 구성
const pipeline = (...fns) => data =>
fns.reduce((value, fn) => fn(value), data);
const process = pipeline(
data => data.filter(x => x > 0),
data => data.map(x => x * 2),
data => data.reduce((sum, x) => sum + x, 0),
);
process([-1, 2, 3, -4, 5]);
// 양수만 필터 [2, 3, 5] -> 2배 [4, 6, 10] -> 합산 20
고차 함수로 전략 주입
// 정렬 전략을 함수로 주입
function sortBy(arr, compareFn) {
return [...arr].sort(compareFn);
}
const people = [
{ name: '홍길동', age: 30 },
{ name: '이순신', age: 45 },
{ name: '강감찬', age: 28 },
];
const byAge = (a, b) => a.age - b.age;
const byName = (a, b) => a.name.localeCompare(b.name);
sortBy(people, byAge); // 나이 순 정렬
sortBy(people, byName); // 이름 순 정렬
클로저와의 관계
고차 함수가 함수를 반환할 때, 반환된 함수는 종종 외부 변수를 캡처하는 클로저가 된다.
function counter(start = 0) {
let count = start; // 클로저로 캡처되는 변수
return {
increment: () => ++count,
decrement: () => --count,
value: () => count,
};
}
const c = counter(10);
c.increment(); // 11
c.increment(); // 12
c.decrement(); // 11
c.value(); // 11
counter 는 객체를 반환하는 HOF. 반환된 세 메서드가 count 를 클로저로 공유한다.
함정
WARNING
this 컨텍스트 손실: function 키워드 콜백에서는 this 가 호출 시 재결정된다. 메서드를 HOF 에 넘길 때 주의.
const obj = {
multiplier: 3,
values: [1, 2, 3],
compute() {
// ❌ function 콜백: this 가 undefined (strict mode)
return this.values.map(function(v) {
return v * this.multiplier; // TypeError
});
},
computeOk() {
// ✅ 화살표 함수: this 를 lexical 로 캡처
return this.values.map(v => v * this.multiplier);
},
};
obj.computeOk(); // [3, 6, 9]
WARNING
reduce 의 빈 배열 + 초기값 미지정: TypeError 가 발생한다.
// ❌ 초기값 없으면 빈 배열에서 TypeError
[].reduce((a, b) => a + b);
// ✅ 항상 초기값 명시
[].reduce((a, b) => a + b, 0); // 0
CAUTION
과도한 커링: 커링 단계가 3단계를 넘으면 타입 추론이 어렵고 디버깅이 힘들어진다. 명시적 함수로 풀어쓰는 편이 낫다.
// ❌ 너무 많은 커링 단계
const f = a => b => c => d => a + b + c + d;
// ✅ 명시적 매개변수
const f = (a, b, c, d) => a + b + c + d;
관련 위키
이 글의 용어 (6개)
- [Javascript] 콜백javascript
- 정의 콜백 (callback): 다른 함수의 인자로 넘겨져, 받은 쪽(caller)이 적절한 시점에 호출하는 함수. 특성이 있어야 가능한 패턴이다. JavaScript 에서는 함…
- [Javascript] 클로저javascript
- 정의 클로저 (Closure): 함수가 자신이 정의된 시점의 외부 변수들을 계속 참조할 수 있는 성질. 함수가 일급 객체 () 인 언어의 자연스러운 결과. 메커니즘은 + 함수 객…
- [Javascript] Arrayjavascript
- 정의 JavaScript 의 는 순서 있는 객체. 정수 키 + . 풍부한 메서드 (map/filter/reduce/...) 가 함수형 스타일을 지원. 생성 핵심 메서드 (반복) …
- [Javascript] Arrow Functionjavascript
- 정의 화살표 함수 (arrow function) 는 ES2015 (ES6) 에서 도입된 함수 표현식. 모양. 더 짧은 문법뿐 아니라 가 lexical 로 바인딩 되는 본질적 차이…
- [Javascript] async/awaitjavascript
- 정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
- 일급 함수javascript
- 정의 일급 함수 (First-Class Function): 함수를 숫자, 문자열, 객체처럼 값으로 다룰 수 있는 언어의 특성. 다음 세 가지가 모두 가능하면 그 언어의 함수는 일…
💬 댓글