SDF 챕터들에서 추출한 핵심 개념: - additive-programming: 가산적 프로그래밍 (전체 관통 테마) - generic-procedures: 제네릭 프로시저 (Ch3, Ch5, Ch6) - combinators: 컴비네이터 (Ch2~Ch5) - partial-information: 부분 정보 (Ch1, Ch4, Ch6, Ch7) - degeneracy: 퇴화성 (Ch1, Ch7) - layered-data: 레이어드 데이터 + 의존성 추적 (Ch2, Ch3, Ch6, Ch7) - propagation: 전파 모델 (Ch1, Ch5, Ch6, Ch7) - domain-specific-language: DSL (Ch2~Ch5) wiki/index.md Concepts 섹션 등록, wiki/log.md 기록 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.7 KiB
title, tags, sources, updated
| title | tags | sources | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|
| 도메인 특화 언어 (DSL) |
|
|
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."
범용 언어로 도메인 문제를 풀면 두 가지 불편이 생긴다:
- 개념의 직접성 부재: 도메인 개념을 언어 구조에 억지로 매핑해야 함
- 가산성 어려움: 새 개념이 추가될 때 기존 코드의 여러 곳을 수정해야 함
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
;; 언어 확장: lazy 평가 지원 추가
(lambda ((lazy x) y) ; x는 필요할 때만 평가
(if (zero? y) 0 (* x y)))
이 확장은 인터프리터의 핸들러를 추가하는 것만으로 이루어진다.
배선도 언어 (Ch7)
전파 모델의 배선도 언어는 또 다른 형태의 DSL이다. 제약(constraint)을 언어의 기본 단위로 삼는 선언적 언어다:
;; 곱셈 제약: 세 변수 중 둘을 알면 나머지를 계산
(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