Software Design for Flexibility (Hanson & Sussman 2021) 전문을 wiki/sources/ 아래 챕터별 한국어 wiki 페이지로 컴파일 - SDF-overview: 전체 개요, 챕터 관계도, 공통 테마 - SDF-ch1: 가산적 프로그래밍 철학, 퇴화성, 유연성 비용 - SDF-ch2: 컴비네이터, DSL, 래퍼, 도메인 모델 - SDF-ch3: 제네릭 프로시저, 자동 미분, 트라이 디스패치 - SDF-ch4: 패턴 매칭, 항 재작성, 단일화, 타입 추론 - SDF-ch5: eval/apply, lazy eval, amb, call/cc - SDF-ch6: 레이어드 데이텀/프로시저, 단위 산술, 의존성 추적 - SDF-ch7: 전파 모델, 부분 정보 결합, 의존성 지향 백트래킹 wiki/index.md Sources 섹션 등록, wiki/log.md 기록 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
6.3 KiB
Markdown
157 lines
6.3 KiB
Markdown
---
|
|
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]]
|