핵심 인사이트 (3줄 요약)
- 본질: Currying (커링)은 다인수 함수를 단인수 함수들의 체인으로 분해하는 기법이고, Lazy Evaluation (지연 평가)은 값이 실제로 필요할 때까지 계산을 미루는 평가 전략이다 — 둘 다 "필요할 때까지 미루는" 함수형 철학을 공유한다.
- 가치: 커링은 부분 적용(Partial Application)으로 재사용 가능한 특화 함수를 쉽게 생성하고, 지연 평가는 불필요한 연산을 아예 실행하지 않아 성능과 메모리를 절감한다.
- 판단 포인트: Java Stream의
filter(),map()등 중간 연산(Intermediate Operation)은 지연 평가 — 터미널 연산(Terminal Operation,collect(),count())이 호출될 때까지 실제 실행을 미룬다.
Ⅰ. 개요 및 필요성
커링의 어원: Haskell Curry (수학자/논리학자)의 이름에서 유래.
일반 함수: f(a, b, c) → result
커링 함수: f(a) → g(b) → h(c) → result
f(a)(b)(c) → result
목적: 함수를 특화(Specialize)시켜 재사용성을 높인다.
// 일반 함수: 두 인수를 한번에 받음
BiFunction<Integer, Integer> add = (a, b) -> a + b;
add.apply(3, 5); // 8
// 커링: 첫 번째 인수만 받아 새 함수 반환
Function<Integer, Function<Integer, Integer>> curriedAdd =
a -> b -> a + b;
Function<Integer, Integer> add3 = curriedAdd.apply(3); // a=3으로 특화
add3.apply(5); // 8
add3.apply(7); // 10
| 개념 | 설명 | 반환 타입 |
|---|---|---|
| Currying (커링) | 모든 인수를 단인수 함수 체인으로 분해 | 항상 단인수 함수 |
| Partial Application (부분 적용) | 일부 인수를 미리 바인딩하여 나머지 인수를 받는 함수 생성 | 다인수 함수 가능 |
f(a, b, c) 커링: f(a) → (b → (c → result)) // 3단계 단인수 함수
f(a, b, c) 부분 적용 (a 고정): g(b, c) → result // 2인수 함수 반환
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Problem │──▶│ Core Idea │──▶│ Expected Gain │
└──────────────┘ └──────────────┘ └──────────────┘
- 📢 섹션 요약 비유: 커링은 볼펜 조립공장처럼 부품(인수)을 하나씩 끼워 넣어 완성하는 것 — 중간에 멈추면 "펜대만 있는 반제품"(부분 적용 함수)이 되고, 나중에 남은 부품을 끼우면 완성된 볼펜(결과값)이 나온다.
Ⅱ. 아키텍처 및 핵심 원리
지연 평가는 표현식이 실제로 사용될 때까지 계산을 미루는 평가 전략이다.
즉시 평가 (Eager Evaluation, 기본 방식):
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> result = new ArrayList<>();
for (int n : nums) {
if (n % 2 == 0) result.add(n * 2); // 모든 원소를 즉시 처리
}
// → 10개 모두 filter 시도, 5개 map 수행
지연 평가 (Lazy Evaluation, Java Stream):
nums.stream()
.filter(n -> n % 2 == 0) // 아직 실행 안 됨 (중간 연산)
.map(n -> n * 2) // 아직 실행 안 됨 (중간 연산)
.findFirst(); // 터미널 연산 → 실행 시작
// → 짝수 첫 번째를 찾으면 즉시 중단 (나머지 원소 처리 안 함)
┌─────────────────────────────────────────────────────────────────┐
│ Java Stream 지연 평가 파이프라인 │
│ │
│ 소스(Source) 중간 연산(Intermediate) 터미널 연산 │
│ ┌────────────┐ ┌────────────────────┐ ┌──────────┐ │
│ │ Collection │─────▶│ .filter() │─────▶│.collect()│ │
│ │ Array │ │ .map() │ │.count() │ │
│ │ Stream.of()│ │ .sorted() │ │.findFirst│ │
│ └────────────┘ │ .distinct() │ │.reduce() │ │
│ │ .limit() │ └────┬─────┘ │
│ │ │ │ │
│ │ ← 아직 실행 안됨 → │ ← 이 시점에 실행! │
│ └────────────────────┘ │ │
│ ▼ │
│ 실제 파이프라인 처리 │
│ (pull 방식으로 │
│ 원소를 하나씩 끌어 │
│ 당겨 처리) │
└─────────────────────────────────────────────────────────────────┘
Haskell은 기본적으로 모든 표현식을 지연 평가한다:
-- 무한 리스트 (즉시 평가라면 무한 루프)
naturals = [1..] -- 자연수 무한 리스트
-- take 10이 호출될 때까지 실제로는 아무것도 계산하지 않음
first10 = take 10 naturals -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
| 항목 | 설명 | 포인트 |
|---|---|---|
| 핵심 역할 | 입력·상태·출력을 분리하는 책임 경계 | 구현보다 경계를 먼저 본다. |
| 제어 지점 | 조건, 이벤트, 정책이 만나는 곳 | 병목과 결합이 생기는 곳이다. |
| 검증 포인트 | 테스트·로그·모니터링으로 확인할 지점 | 운영 가능성이 설계 품질을 결정한다. |
- 📢 섹션 요약 비유: 지연 평가는 레스토랑 메뉴판처럼 — 메뉴를 훑어볼 때는 요리를 만들지 않고, "이것 주세요(터미널 연산)" 라고 할 때 비로소 주방이 가동된다.
Ⅲ. 비교 및 연결
| 관점 | 즉시 평가 (Eager) | 지연 평가 (Lazy) |
|---|---|---|
| 실행 시점 | 표현식 정의 즉시 | 결과가 필요할 때 |
| 메모리 사용 | 중간 컬렉션 생성 | 스트림으로 원소 단위 처리 |
| 단락 평가 | 불가 (모두 처리) | 가능 (필요 시 중단) |
| 무한 데이터 | 불가 | 가능 |
| 디버깅 | 쉬움 | 상대적으로 어려움 |
| 대표 언어 | Java (기본), Python | Haskell, Java Stream |
| 패턴 | 설명 | 예시 |
|---|---|---|
| 로거 커링 | 로그 레벨을 미리 바인딩 | log("ERROR")("메시지") |
| 검증기 커링 | 검증 규칙을 미리 바인딩 | validate(schema)(data) |
| API 요청 커링 | 기본 URL을 미리 바인딩 | request(baseUrl)(endpoint)(params) |
| 세금 계산기 커링 | 세율을 미리 바인딩 | tax(0.1)(price) |
- 📢 섹션 요약 비유: 커링은 "세팅 버튼이 있는 커피 머신" — 원두(첫 번째 인수)를 넣어두면 언제든 물(두 번째 인수)만 붓으면 커피(결과)가 나오는 것처럼, 공통 설정을 미리 바인딩해두고 나머지만 나중에 제공한다.
Ⅳ. 실무 적용 및 기술사 판단
// 함수형 인터페이스를 활용한 커링
public static <A, B, C> Function<A, Function<B, C>> curry(BiFunction<A, B, C> f) {
return a -> b -> f.apply(a, b);
}
// 실제 사용
Function<String, Function<String, String>> greet =
curry((greeting, name) -> greeting + ", " + name + "!");
Function<String, String> hello = greet.apply("Hello"); // "Hello" 바인딩
Function<String, String> bye = greet.apply("Goodbye"); // "Goodbye" 바인딩
System.out.println(hello.apply("Alice")); // "Hello, Alice!"
System.out.println(hello.apply("Bob")); // "Hello, Bob!"
System.out.println(bye.apply("Alice")); // "Goodbye, Alice!"
// 1억 개 요소에서 짝수이고 100 미만인 첫 3개만 필요할 때
List<Integer> result = IntStream.range(1, 100_000_001)
.filter(n -> n % 2 == 0) // 지연: 아직 실행 안 됨
.filter(n -> n < 100) // 지연: 아직 실행 안 됨
.limit(3) // 지연: 아직 실행 안 됨
.boxed()
.collect(Collectors.toList()); // 터미널: 이제 실행
// 결과: [2, 4, 6] — 2, 4, 6을 찾는 순간 처리 중단
// 1억 개를 모두 처리하지 않음!
| 설계 요소 | 커링 | 지연 평가 |
|---|---|---|
| 재사용성 | 부분 적용으로 특화 함수 생성 | 불필요한 연산 제거 |
| 메모리 | 함수 객체 생성 비용 | 중간 컬렉션 생성 제거 |
| 무한 자료구조 | 무관 | 무한 스트림 처리 가능 |
| 적용 언어/프레임워크 | Scala, Haskell, JavaScript | Java Stream, Haskell, Kotlin |
판단 체크리스트
- 해결하려는 변화 축이 분명한가?
- 추상화 비용보다 변경 절감 효과가 큰가?
- 테스트·로그·운영 가시성이 확보되는가?
- 팀이 이 구조를 일관되게 유지할 수 있는가?
- 📢 섹션 요약 비유: 지연 평가는 영화 스트리밍 — 영화 전체를 다운로드(즉시 평가)하는 게 아니라 보는 부분만 버퍼링(지연 평가)하여 1시간짜리 영화도 재생 즉시 시작할 수 있다.
Ⅴ. 기대효과 및 결론
커링과 지연 평가는 함수형 프로그래밍의 두 핵심 기둥이다:
커링의 가치:
- 함수를 재사용 가능한 조각으로 분해
- 부분 적용으로 특화 함수 라이브러리 구성
- 함수 합성의 기반 제공
지연 평가의 가치:
- 불필요한 연산 완전 제거
- 무한 자료구조 처리 가능
- 단락 평가(Short-Circuit Evaluation)로 조기 종료 가능
Java에서는 Stream API를 통해 지연 평가를 실용적으로 활용할 수 있으며, Function<A, Function<B, R>> 타입으로 커링을 구현할 수 있다. 기술사 시험에서는 Java Stream의 중간 연산/터미널 연산 구분과 **커링을 통한 함수 특화(Specialize)**를 명확히 설명하는 것이 핵심이다.
확장 방향은 ① 선언형 API와의 결합, ② 관측 가능성(Observability) 내장, ③ 분산 환경에 맞는 변형 패턴 적용이다.
- 📢 섹션 요약 비유: 커링은 "재료를 미리 손질해두는 밀프렙(Meal Prep)" — 미리 재료(인수)를 준비해두면 식사 시간(실행 시점)에 빠르게 완성할 수 있고, 지연 평가는 "배고플 때만 요리하는 것" — 배고프지 않으면(결과가 필요 없으면) 아예 불을 켜지 않는다.
📌 관련 개념 맵
| 관계 | 개념 | 설명 |
|---|---|---|
| 상위 개념 | 함수형 프로그래밍 (Functional Programming) | 커링/지연 평가가 속하는 패러다임 |
| 연관 개념 | 모나드 (Monad) | flatMap 체이닝과 지연 평가의 결합 |
| 연관 개념 | 부분 적용 (Partial Application) | 커링의 실용적 변형 |
| 구현체 | Java Stream API | 지연 평가의 대표 구현체 |
| 구현체 | Haskell | 커링과 지연 평가를 기본 채택한 언어 |
| 연관 개념 | 불변 객체 (Immutable Object) | 함수형 프로그래밍의 또 다른 핵심 원칙 |
| 연관 개념 | 함수 합성 (Function Composition) | 커링이 가능하게 하는 합성 패턴 |
📈 관련 키워드 및 발전 흐름도
부분 적용 → 커링과 지연 평가 → 스트림 최적화
👶 어린이를 위한 3줄 비유 설명
- 커링은 조각 케이크 만들기야 — "케이크(초코맛)(딸기 장식)(생크림)" 처럼 재료를 하나씩 추가하면서 원하는 케이크를 만들 수 있어.
- 지연 평가는 숙제를 "제출할 때" 하는 것과 같아 — 선생님이 검사하러 올 때(터미널 연산)만 실제로 계산하고, 그 전까지는 "나중에 할게" 라고 계획만 세워둬.
- Java Stream은 지연 평가 덕분에 100억 개의 숫자에서 딱 10개만 필요하면 10개를 찾는 순간 멈춰서 나머지 99억 9999만 9990개는 아예 처리하지 않아 — 정말 필요한 것만 한다!