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