---
title: 도메인 특화 언어 (DSL)
tags: [concept, SDF]
sources: [SDF-ch2-dsl, SDF-ch3-generic-procedures, SDF-ch4-pattern-matching, SDF-ch5-evaluation]
updated: 2026-04-30
---

# 도메인 특화 언어 (DSL)

## 한 줄 정의

문제 도메인의 개념과 관계를 직접 반영하는 어휘와 구조를 가진 언어로, 해당 도메인의 문제를 그 언어로 자연스럽게 표현할 수 있게 하여 도메인 전문가와 프로그래머 사이의 간격을 줄이고 가산적 확장을 가능하게 한다.

## 핵심 내용

### DSL의 스펙트럼

SDF에서 DSL은 단일한 기법이 아니라 구현 깊이에 따른 스펙트럼이다:

**1단계: 컴비네이터 기반 임베디드 DSL (Ch2)**
- 호스트 언어(Scheme) 함수들을 컴비네이터로 조합
- 별도의 파서 없음. 호스트 언어의 문법을 그대로 사용
- 예: 정규표현식 컴비네이터, 함수 컴비네이터

**2단계: 제네릭 프로시저 기반 도메인 언어 (Ch3)**
- 도메인의 연산(더하기, 곱하기)이 제네릭 프로시저
- 새 타입이 도입될 때마다 핸들러를 추가해 도메인을 확장
- 예: 숫자 → 기호 → 함수 → 미분 객체로 확장되는 산술

**3단계: 패턴 기반 규칙 언어 (Ch4)**
- 데이터 변환을 패턴+귀결 규칙으로 표현
- `rule-simplifier`가 고정점까지 규칙들을 자동 적용
- 예: 대수 단순화 규칙들, 체스 규칙

**4단계: 완전한 인터프리터 (Ch5)**
- 새 언어를 완전히 정의: 구문, 의미, 실행 모델
- 가장 강력하지만 가장 많은 투자가 필요
- 예: lazy eval, amb를 포함한 Scheme 인터프리터

### 왜 DSL인가

> "One of the best ways to attack a problem is to make up a domain-specific language in which the solution is easily expressed."

> "Programmers should know how to escape the confines of whatever programming language they must use by making an interpreter for a language that is more appropriate for expressing the solution."

범용 언어로 도메인 문제를 풀면 두 가지 불편이 생긴다:
1. **개념의 직접성 부재**: 도메인 개념을 언어 구조에 억지로 매핑해야 함
2. **가산성 어려움**: 새 개념이 추가될 때 기존 코드의 여러 곳을 수정해야 함

DSL은 도메인 개념이 언어의 일급 시민이 되므로 두 문제가 모두 해결된다.

### 도메인 모델 분리: 체커 게임 사례

Ch2의 체커 게임 심판 구현은 DSL 설계의 좋은 교육 사례다:

**1단계 — 모놀리식**: 킹(king), 점프(jump) 같은 체커 특화 개념이 코드 전체에 분산됨. 새 규칙을 추가하려면 여러 곳을 수정해야 함.

**2단계 — 도메인 모델 분리**: 
- `partial-move(pmove)` 추상화: 이동 경로를 표현하는 도메인 객체
- **룰 익스큐티브(rule executive)**: 제어 구조를 규칙에서 분리
- **진화 규칙(evolution rules)**: pmove를 새 pmove들로 변환
- **집합 규칙(aggregate rules)**: 완료된 pmove 집합에 작용
- 각 체커 규칙이 단일 프로시저로 표현됨

이제 새 규칙(예: "룰 변형" 게임)을 추가하는 것은 새 프로시저를 등록하는 것이다. 기존 규칙 프로시저들은 그대로다.

도메인 모델 = 도메인 언어의 프리미티브 + 조합 수단 + 추상화 수단.

### 정규표현식: DSL의 나쁜 예

> "Regular expressions are a beautiful example of how *not* to build a system."

전통 정규표현식의 문제:
- 컨텍스트 의존성: `^`, `.`, `*`이 위치에 따라 의미가 다름
- 조합 불가능: `(abc)|(def)` 조각은 독립적으로 재사용할 수 없음
- 가독성: 복잡한 패턴은 해독이 불가능

컴비네이터 기반 대안은 각 부품이 독립적이고, 조합이 안전하며, 의미가 명확하다. 성능이 약간 낮을 수 있지만, 컴파일러로 POSIX BRE로 변환하면 해결된다.

### 인터프리터: DSL의 궁극적 형태

인터프리터를 만드는 것은 새 언어를 만드는 것이다. Ch5의 인터프리터는 가산적으로 확장 가능하다:

- `g:eval`을 제네릭 프로시저로 구현: 새 표현식 타입은 새 핸들러 추가
- 분석/실행 분리: 새 평가 전략(lazy, amb)은 실행 단계만 수정
- lazy eval: 매개변수 선언으로 평가 전략 지정 → 선언적 DSL

```scheme
;; 언어 확장: lazy 평가 지원 추가
(lambda ((lazy x) y)  ; x는 필요할 때만 평가
  (if (zero? y) 0 (* x y)))
```

이 확장은 인터프리터의 핸들러를 추가하는 것만으로 이루어진다.

### 배선도 언어 (Ch7)

전파 모델의 배선도 언어는 또 다른 형태의 DSL이다. 제약(constraint)을 언어의 기본 단위로 삼는 선언적 언어다:

```scheme
;; 곱셈 제약: 세 변수 중 둘을 알면 나머지를 계산
(c:* a b c)

;; 삼각함수 제약
(c:tan angle ratio)
```

이 언어에서 "프로그램"은 제약들의 네트워크다. 제어 흐름이 없다.

## SDF에서의 등장

- [[SDF-ch2-dsl]]: 핵심 챕터. 컴비네이터 기반 임베디드 DSL, 정규표현식 DSL, 래퍼, 체커 도메인 모델
- [[SDF-ch3-generic-procedures]]: 제네릭 산술이 도메인별 확장을 지원하는 DSL의 기반 인프라
- [[SDF-ch4-pattern-matching]]: 패턴+규칙 시스템이 변환 DSL. 대수 단순화 규칙들이 대수 DSL
- [[SDF-ch5-evaluation]]: 완전한 인터프리터 구현. lazy eval과 amb를 DSL 확장으로 추가하는 과정

## 실천 시 주의점

**DSL의 복잡성 비용**: DSL을 만들면 DSL 자체를 배워야 하는 학습 비용이 생긴다. "이 DSL을 쓰는 사람이 나 혼자인가?"를 먼저 물어야 한다. 팀의 크기와 도메인의 복잡성이 DSL 투자를 정당화해야 한다.

**임베디드 vs 독립 DSL**: 임베디드 DSL(EDSL)은 호스트 언어의 도구를 모두 쓸 수 있지만, 호스트 언어의 문법 제약을 받는다. 독립 DSL은 문법 자유도가 높지만 파서·인터프리터 구현 비용이 크다. 대부분의 경우 EDSL이 더 실용적이다.

**도메인 모델의 안정성**: DSL은 도메인 모델이 안정적일 때 투자할 가치가 있다. 도메인 모델이 자주 변하면 DSL 자체를 수정하는 비용이 DSL이 주는 이점을 상회한다.

**에러 메시지 품질**: 사용자가 DSL을 잘못 사용했을 때 의미 있는 에러 메시지를 제공해야 한다. 호스트 언어의 에러가 DSL 사용자에게 노출되면 "내부 구현"이 드러나 추상화가 새는 것이다.

## 관련 개념

- [[additive-programming]] — DSL은 가산적 확장이 자연스러운 언어 레벨 추상화
- [[combinators]] — 임베디드 DSL의 핵심 구현 메커니즘
- [[generic-procedures]] — 제네릭 프로시저로 도메인 연산을 확장 가능하게 구현
- [[propagation]] — 배선도 언어 자체가 제약 기반 DSL
