Conceptly
← 전체 목록
💤

Lazy Evaluation

기반 원칙값이 필요할 때까지 계산을 미루는 전략

지연 평가는 값을 정의한 순간 즉시 계산하지 않고, 그 값이 실제로 필요해질 때까지 계산을 미루는 전략입니다. 표현식은 먼저 '어떻게 계산할지'로 보관되고, 소비 지점이 생기면 그때 비로소 실행됩니다. 이 전략 덕분에 끝이 없는 시퀀스도 다룰 수 있고, 최종 결과의 일부만 필요할 때는 나머지 계산을 건너뛸 수 있습니다. 함수형 프로그래밍에서 계산 시점 자체를 설계의 일부로 다루는 대표 개념입니다.

아키텍처 다이어그램

🔄 프로세스 다이어그램

점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다

왜 필요한가요?

즉시 평가(eager evaluation)에서는 표현식을 만드는 순간 계산이 일어납니다. 배열에 map, filter를 길게 체이닝해 놓고 마지막에 앞의 10개 항목만 쓰더라도, 중간 단계가 전부 먼저 실행되어 중간 배열이 만들어집니다. 조건문에서 앞쪽 값만으로 답이 정해져도 뒤쪽의 비싼 계산이 미리 실행되면 낭비가 생깁니다. 더 극단적으로는 자연수 전체 같은 무한 시퀀스를 즉시 평가 방식으로는 아예 표현할 수 없습니다. '지금 꼭 필요한 부분만 계산한다'는 다른 실행 전략이 필요합니다.

왜 이런 방식이 등장했나요?

함수형 언어 중 Haskell은 지연 평가를 기본 실행 모델로 채택해, 무한 리스트와 고수준 합성을 자연스럽게 다루도록 했습니다. 주류 언어는 대개 즉시 평가를 기본으로 하지만, generator, iterator, stream, short-circuit 연산자처럼 지연 평가의 조각들을 꾸준히 받아들였습니다. 대용량 데이터 처리와 반응형 프로그래밍이 중요해지면서, '언제 계산할 것인가'는 성능 최적화가 아니라 API 설계 문제로까지 올라왔습니다. 지연 평가는 계산량과 메모리 사용량을 소비 패턴에 맞춰 조절하려는 흐름 속에서 더 자주 논의되기 시작했습니다.

내부적으로 어떻게 동작하나요?

지연 평가는 보통 계산을 직접 실행하지 않고 thunk, iterator, generator 같은 래퍼 안에 넣어 두는 방식으로 구현됩니다. 이 래퍼는 '나중에 계산하는 방법'만 들고 있고, 실제 값은 아직 없습니다. 소비자가 next()를 호출하거나, 조건식이 오른쪽 피연산자를 정말 필요로 하거나, UI가 특정 구간 데이터를 실제로 렌더링할 때 계산이 트리거됩니다. 그때도 전체를 한 번에 계산하지 않고 필요한 부분만 평가할 수 있습니다. 어떤 구현은 한 번 계산한 결과를 캐시해 재사용하고, 어떤 구현은 매 요청마다 다시 계산합니다. 핵심은 계산 시점이 정의 시점이 아니라 소비 시점으로 밀려 있다는 점입니다.

코드로 보면

필요한 순간에만 값을 만드는 generator

function* naturals() {
  let n = 0;
  while (true) {
    yield n;
    n += 1;
  }
}

const nums = naturals();
nums.next().value;  // 0
nums.next().value;  // 1
nums.next().value;  // 2

// 전체 자연수를 미리 만들지 않고,
// next()가 호출될 때마다 하나씩 계산합니다.

generator는 모든 값을 미리 만들지 않습니다. 소비자가 next()를 호출할 때마다 그 순간 필요한 다음 값만 계산해 내보내므로, 무한 시퀀스도 다룰 수 있습니다.

경계와 구분

지연 평가는 map, filter, reduce 같은 함수형 변환과 경쟁하는 개념이 아니라 계산 시점을 다루는 개념입니다. 같은 변환 파이프라인이라도 배열 메서드 체인은 보통 각 단계를 즉시 실행해 중간 결과를 만들고, 지연 평가는 소비자가 필요로 하는 순간까지 그 계산을 미룹니다. 이 차이는 값의 의미보다는 실행 타이밍과 비용 구조에 영향을 줍니다. 다만 지연 평가와 부작용은 긴장 관계가 있습니다. 계산 시점이 뒤로 밀리면 로그, 네트워크 호출, 예외가 언제 발생하는지 직관이 흐려질 수 있기 때문입니다. 그래서 지연 평가는 순수한 계산과 함께 쓸 때 가장 예측 가능하고, 부작용이 섞이면 실행 타이밍을 더 조심해서 다뤄야 합니다.

트레이드오프

Gain 필요한 값만 계산하므로 큰 데이터 흐름에서 불필요한 연산과 메모리 사용을 줄일 수 있고, 무한 시퀀스 같은 표현도 가능해집니다. Cost 실제 계산이 언제 일어나는지 코드 표면만 보고는 덜 분명해질 수 있습니다. 잘못 설계하면 계산이 너무 늦게 터져 디버깅이 어려워지고, 평가를 미룬 표현식이 오래 살아남아 메모리를 붙잡는 공간 누수(space leak) 문제가 생길 수 있습니다. Decision Scale 전체 결과를 다 소비하지 않는 흐름, 스트리밍, 긴 파이프라인, 무한 또는 매우 큰 데이터 구조에서는 지연 평가의 이점이 큽니다. 반대로 데이터가 작고 실행 시점을 명확히 드러내는 것이 더 중요한 코드에서는 즉시 평가가 더 단순합니다.

언제 쓰나요?

지연 평가는 generator 기반 데이터 소비, iterator 파이프라인, 검색 결과의 앞부분만 읽는 스트리밍 처리, 조건이 맞을 때만 무거운 계산을 수행하는 fallback 로직에서 자주 쓰입니다. 프론트엔드에서는 긴 목록을 한 번에 다 계산하지 않고 보이는 구간만 준비하는 방식으로도 연결됩니다. 실무에서는 '이 값을 정말 지금 전부 계산해야 하는가?'라는 질문을 던질 때 지연 평가가 후보로 올라옵니다. 계산량이 아니라 소비 방식이 설계를 바꾸는 지점을 잡아내는 것이 핵심입니다.

대용량 데이터 파이프라인무한 시퀀스조건 분기비싼 fallback 계산