---
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]]
