--- title: "Ch3: Variations on an Arithmetic Theme" tags: [source, SDF] source: "Software Design for Flexibility, Hanson & Sussman (2021)" chapter: 3 updated: 2026-04-30 --- # Ch3: Variations on an Arithmetic Theme ## 핵심 아이디어 **술어-디스패치 제네릭 프로시저(predicate-dispatched generic procedures)**를 중심으로, 기존 프로그램의 동작을 확장하는 강력하지만 위험한 기법을 다룬다. 산술 연산의 의미를 수치에서 기호, 함수, 미분 객체로 확장하는 과정을 통해 제네릭 시스템의 설계 원칙을 보여준다. ## 주요 개념 ### 3.1 산술 패키지 컴비네이터 **산술 패키지(arithmetic package)**: 연산자 이름에서 구현으로의 매핑. `install-arithmetic!`으로 사용자 환경에 설치. **적용 가능성 명세(applicability specification)**: 술어 리스트의 리스트. 인수들이 케이스 중 하나를 만족하면 해당 핸들러 적용. ```scheme ;; 수치 산술: (number? number?) 케이스만 적용 ;; 기호 산술: (number? symbolic?), (symbolic? number?), ;; (symbolic? symbolic?) 케이스에 적용 (define combined-arithmetic (extend-arithmetic symbolic-extender numeric-arithmetic)) ``` `add-arithmetics` 컴비네이터로 산술들을 결합. 그러나 컴비네이터 방식은 한계가 있다: - 부품의 형태를 미리 결정해야 함 - 계층화된 구조(예: 함수 산술)를 나중에 확장하기 어려움 - 자기 참조 구조 불가 ### 3.2 확장 가능한 제네릭 프로시저 컴비네이터의 한계를 극복하기 위해 **동적으로 확장 가능한 제네릭 프로시저** 도입. ```scheme ;; 생성 (define plus (simple-generic-procedure 'plus 2 #f)) ;; 핸들러 추가 (define-generic-procedure-handler plus (all-args 2 number?) (lambda (a b) (+ a b))) (define-generic-procedure-handler plus (any-arg 2 symbolic? number?) (lambda (a b) (list '+ a b))) ``` **구현 메커니즘**: 1. `generic-procedure-constructor`: 디스패치 전략을 인자로 받아 제네릭 프로시저 생성기 반환 2. `dispatch-store`: 핸들러 저장/검색 전략 캡슐화 3. 메타데이터 테이블: 제네릭 프로시저에 "스티키 노트"로 규칙 목록 첨부 **제네릭 산술의 폐쇄성(closure)**: 모든 확장을 제네릭 산술 자체를 기반으로 만들면 자기 참조 구조가 가능하다. ```scheme (let ((g (make-generic-arithmetic make-simple-dispatch-store))) (add-to-generic-arithmetic! g numeric-arithmetic) (extend-generic-arithmetic! g symbolic-extender) (extend-generic-arithmetic! g function-extender) (install-arithmetic! g)) ``` **주의**: 핸들러 추가 순서에 의존성이 생길 수 있다. 적용 가능성이 겹치는 규칙들의 순서에 따라 결과가 달라짐. ### 3.3 자동 미분 (Automatic Differentiation) 제네릭 프로시저의 탁월한 응용 예시. **미분 객체(differential object)** `[x, δx]`를 새 데이터 타입으로 도입: $$[x, \delta x] \xrightarrow{f} [f(x), Df(x)\delta x]$$ 각 산술 연산에 미분 객체 처리 핸들러를 추가하기만 하면, 함수의 합성을 통해 연쇄 법칙이 자동으로 적용된다. ```scheme (define (derivative f) (define (the-derivative x) (let* ((dx (make-new-dx)) (value (f (d:+ x (make-infinitesimal dx))))) (extract-dx-part value dx))) the-derivative) ;; sqrt의 미분 핸들러 (define diff:sqrt (diff:unary-proc sqrt (lambda (x) (/ 1 (* 2 (sqrt x)))))) ``` 고차 함수(함수를 반환하는 함수)에서의 미분은 기술적 복잡성이 있다: dx 태그 충돌 문제를 `replace-dx` 메커니즘으로 해결. ### 3.4 효율적인 디스패치 **단순 선형 스캔의 문제**: 많은 규칙에서 동일한 술어를 반복 호출. **트라이(Trie) 기반 디스패치**: - 인수 시퀀스를 트라이로 인덱싱 - 첫 번째 인수의 타입으로 후보 규칙 집합을 좁힘 - 중복 술어 평가 제거 **캐싱**: - 인수의 구현 타입 태그를 키로 사용 - 이전 디스패치 결과를 해시 테이블에 캐시 - `cache-wrapped-dispatch-store`로 임의의 디스패치 전략에 캐싱 추가 ### 3.5 사용자 정의 타입 **추상 술어(abstract predicate)**: 비용이 높은 술어 평가를 메모이즈. 객체에 태그를 붙여 이후 디스패치에서 비싼 술어 재평가 불필요. ```scheme (define prime-number? (simple-abstract-predicate 'prime-number slow-prime?)) (set-predicate<=! prime-number? exact-integer?) ;; 이후 exact-integer? 도 prime-number 객체를 인식 ``` **서브타입 관계 선언**: `set-predicate<=!`로 부분집합 관계 명시. 가장 특수적인 핸들러가 선택됨. ### 3.5.4 어드벤처 게임 제네릭 프로시저와 사용자 정의 타입의 실용적 응용 예시. ```scheme ;; 이동 행동이 4개 인수 타입의 조합에 따라 달라짐 (define generic-move! (most-specific-generic-procedure 'generic-move! 4 #f)) (define-generic-procedure-handler generic-move! (match-args mobile-thing? place? bag? person?) ...) (define-generic-procedure-handler generic-move! (match-args mobile-thing? bag? place? person?) ...) (define-generic-procedure-handler generic-move! (match-args person? place? place? person?) ...) ``` 단일 디스패치 OOP와 달리, 모든 인수 타입의 조합에 따라 행동이 결정되므로 더 자연스러운 분해가 가능하다. ## 핵심 인용 > "Systems built by combinators, as in section 3.1, result in beautiful diamond-like systems. This is sometimes the right idea... but it is very hard to add to a diamond. If a system is built as a ball of mud, it is easy to add more mud." > "Construction of a system on a substrate of extensible generic procedures is a powerful idea... a user may extend them to support arithmetic on quaternions, vectors, matrices, integers modulo a prime, functions, tensors, differential forms..." > "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures." (Alan Perlis) ## 관련 개념 - [[SDF-ch2-dsl]] — 컴비네이터 기반 접근의 한계에서 제네릭 프로시저로 이행 - [[SDF-ch4-pattern-matching]] — 제네릭 프로시저의 패턴 기반 확장 - [[SDF-ch6-layering]] — 제네릭 프로시저와 밀접한 레이어링 - [[SDF-ch7-propagation]] — 타입 태그와 의존성 추적의 결합 - [[SDF-overview]]