SDF 챕터들에서 추출한 핵심 개념: - additive-programming: 가산적 프로그래밍 (전체 관통 테마) - generic-procedures: 제네릭 프로시저 (Ch3, Ch5, Ch6) - combinators: 컴비네이터 (Ch2~Ch5) - partial-information: 부분 정보 (Ch1, Ch4, Ch6, Ch7) - degeneracy: 퇴화성 (Ch1, Ch7) - layered-data: 레이어드 데이터 + 의존성 추적 (Ch2, Ch3, Ch6, Ch7) - propagation: 전파 모델 (Ch1, Ch5, Ch6, Ch7) - domain-specific-language: DSL (Ch2~Ch5) wiki/index.md Concepts 섹션 등록, wiki/log.md 기록 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.1 KiB
title, tags, sources, updated
| title | tags | sources | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|
| 컴비네이터 |
|
|
2026-04-30 |
컴비네이터
한 줄 정의
원시 부품(primitives)과 조합 수단(combinators)으로 이루어진 언어 체계에서, 조합의 인터페이스 명세가 원시 부품의 인터페이스 명세와 동일하도록 설계된 부품 조합기.
핵심 내용
컴비네이터 시스템의 정의와 핵심 성질
SDF의 정의:
"A system of combinators is a set of primitive parts and a set of means of combining parts such that the interface specifications of the combinations are the same as those of the primitives."
이 정의에서 핵심은 인터페이스 동일성이다. 조합의 결과가 원시 부품과 동일한 인터페이스를 가지므로, 재귀적 조합이 무제한으로 가능하다. 어떤 조합도 법적으로 유효한(legally correct) 프로그램을 만들어낸다.
네 가지 핵심 성질:
- 믹스앤매치: 임의의 부품 조합이 가능
- 법적 정확성: 모든 조합이 타입 오류 없이 유효
- 컨텍스트 독립성: 부품의 동작이 조합 맥락에 영향받지 않음
- 비간섭성: 새 부품·컴비네이터 추가가 기존 프로그램에 영향 없음
기본 함수 컴비네이터
;; compose: g 다음 f (f ∘ g)
(define (compose f g)
(define (the-composition . args)
(call-with-values (lambda () (apply g args)) f))
(restrict-arity the-composition (get-arity g)))
;; parallel-combine: f와 g를 같은 인수에 적용, 결과를 h에 전달
(define (parallel-combine h f g)
(define (the-combination . args)
(h (apply f args) (apply g args)))
the-combination)
;; spread-combine: 인수를 두 그룹으로 나눠 f와 g에 각각 적용
(define (spread-combine h f g)
(compose h (spread-apply f g)))
아리티(arity) 관리: 컴비네이터가 올바르게 작동하려면 각 함수의 인수 개수를 추적해야 한다. restrict-arity와 get-arity로 함수에 아리티를 메타데이터로 첨부한다(해시 테이블). 이는 레이어링 아이디어의 초기 형태이기도 하다.
컴비네이터로 DSL 만들기: 정규표현식 사례
SDF Ch2는 정규 표현식을 나쁜 설계의 예시로 들고, 컴비네이터 기반 대안을 제시한다:
;; 나쁜 예: 정규표현식 문자열
"^[a-z].*[0-9]$" ; 컨텍스트에 따라 의미가 달라지고 조합이 어렵다
;; 좋은 예: 컴비네이터 기반 DSL
(r:seq (r:bol)
(r:char-from "abcdefghijklmnopqrstuvwxyz")
(r:repeat 0 #f (r:dot))
(r:char-from "0123456789")
(r:eol))
컴비네이터 기반 DSL은 각 부품이 독립적이고, 조합이 안전하며, 의미가 맥락에 무관하다.
컴비네이터가 채택되는 영역들
SDF 전체에서 컴비네이터 패턴이 반복 등장한다:
- Ch2: 함수 컴비네이터(
compose,parallel-combine), 정규표현식 컴비네이터 - Ch3: 산술 패키지 컴비네이터(
extend-arithmetic,add-arithmetics) - Ch4: 매처 컴비네이터 — 패턴이 매처들의 컴비네이터 조합으로 컴파일됨
- Ch5: 실행 프로시저들이 컴비네이터 시스템을 이룸 (분석/실행 분리 단계)
컴비네이터의 한계: 다이아몬드와 진흙
"Systems built by combinators result in beautiful diamond-like systems. This is sometimes the right idea... but it is very hard to add to a diamond."
컴비네이터 시스템이 완성된 이후 확장하려면, 기존 컴비네이터 구조 밖에서 새 부품을 만들어 합치는 방법이 필요하다. 이것이 Ch3에서 제네릭 프로시저로 이행하는 이유다.
컴비네이터의 폐쇄성(closure)이 장점이자 단점이다. 조합이 원시 부품과 같은 타입이라는 성질이 무한 조합을 가능하게 하지만, 그 타입 체계 바깥으로 나가기 어렵게 만든다.
아키텍트 관점에서: 컴비네이터는 설계 공간이 안정적이고 미리 알려진 도메인에 적합하다. 요구사항이 변동적이거나 타입이 동적으로 추가되어야 하면 제네릭 프로시저가 낫다.
SDF에서의 등장
- SDF-ch2-dsl: 핵심 챕터. 함수 컴비네이터(
compose,parallel-combine,spread-combine) 구현, 아리티 관리, 정규표현식 DSL, 단위 래퍼, 체커 도메인 모델 - SDF-ch3-generic-procedures: 산술 패키지를 컴비네이터로 결합하는 시도와 그 한계 분석. 컴비네이터에서 제네릭 프로시저로의 이행 동기 설명
- SDF-ch4-pattern-matching: 매처들이 컴비네이터 방식으로 구성됨. 패턴 컴파일 = 매처 컴비네이터 조합
- SDF-ch5-evaluation: 실행 프로시저(execution procedures)들이 CPS 컴비네이터 시스템을 구성함
실천 시 주의점
아리티 추적의 부담: 함수가 몇 개의 인수를 받는지를 런타임에 추적해야 컴비네이터가 올바르게 작동한다. 언어가 이를 지원하지 않으면 별도의 메타데이터 관리가 필요하다.
에러 메시지 품질: 잘못된 조합에서의 에러가 어느 부품에서 발생했는지 추적하기 어렵다. 조합이 깊어질수록 스택 트레이스가 의미없어진다.
컴비네이터 수의 폭발: 모든 유용한 조합을 컴비네이터로 제공하려다 보면 컴비네이터 수가 폭발적으로 늘어날 수 있다. 최소한의 직교 컴비네이터 집합을 설계하는 것이 중요하다.
맥락 독립성 보장: 컴비네이터의 핵심 성질인 컨텍스트 독립성을 실제로 보장하기 위해서는 부품들이 부수 효과(side effect)나 전역 상태에 의존하지 않아야 한다.
관련 개념
- additive-programming — 컴비네이터는 가산적 프로그래밍의 기반 도구
- generic-procedures — 컴비네이터의 한계를 넘어서는 동적 확장 메커니즘
- domain-specific-language — 컴비네이터 시스템이 DSL의 구조적 기반
- layered-data — 아리티 첨부에서 시작된 레이어링 아이디어의 확장