핵심 인사이트 (3줄 요약)

  1. 본질: Currying (커링)은 다인수 함수를 단인수 함수들의 체인으로 분해하는 기법이고, Lazy Evaluation (지연 평가)은 값이 실제로 필요할 때까지 계산을 미루는 평가 전략이다 — 둘 다 "필요할 때까지 미루는" 함수형 철학을 공유한다.
  2. 가치: 커링은 부분 적용(Partial Application)으로 재사용 가능한 특화 함수를 쉽게 생성하고, 지연 평가는 불필요한 연산을 아예 실행하지 않아 성능과 메모리를 절감한다.
  3. 판단 포인트: 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 (기본), PythonHaskell, 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, JavaScriptJava Stream, Haskell, Kotlin

판단 체크리스트

  1. 해결하려는 변화 축이 분명한가?
  2. 추상화 비용보다 변경 절감 효과가 큰가?
  3. 테스트·로그·운영 가시성이 확보되는가?
  4. 팀이 이 구조를 일관되게 유지할 수 있는가?
  • 📢 섹션 요약 비유: 지연 평가는 영화 스트리밍 — 영화 전체를 다운로드(즉시 평가)하는 게 아니라 보는 부분만 버퍼링(지연 평가)하여 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줄 비유 설명

  1. 커링은 조각 케이크 만들기야 — "케이크(초코맛)(딸기 장식)(생크림)" 처럼 재료를 하나씩 추가하면서 원하는 케이크를 만들 수 있어.
  2. 지연 평가는 숙제를 "제출할 때" 하는 것과 같아 — 선생님이 검사하러 올 때(터미널 연산)만 실제로 계산하고, 그 전까지는 "나중에 할게" 라고 계획만 세워둬.
  3. Java Stream은 지연 평가 덕분에 100억 개의 숫자에서 딱 10개만 필요하면 10개를 찾는 순간 멈춰서 나머지 99억 9999만 9990개는 아예 처리하지 않아 — 정말 필요한 것만 한다!