DSL 이란 무엇인가?
https://en.wikipedia.org/wiki/Domain-specific_language
DSL은 Domain Specific Language 즉 도메인 특화 언어의 약자이다. 사실 이 책을 보기 전에는 DSL에 대해 공부해본 적이 없었다.
일단 말 그대로의 뜻으로 생각해봤다. 특정 도메인에 특화된 언어. 간단하게 조사하고 생각해봤을때 객체와 DB를 자동으로 매핑해주는 ORM이 떠올랐다.
어쨌던 예시를 들어 이해해보면 프로그래밍 언어에서의 DB 조작을 쉽게 해주는 기능이 사실 원래는 프로그래밍 코드에서 DB등을 조작하려면 여러 API를 조합하거나, 쿼리문을 이용해야 하지만, DSL을 잘 설계하면 이러한 번거로움을 덜 수 있게 해준다. 정도로 이해해봤다.
훌륭한 API란 무엇인가?
사실 처음에는 DSL과 API가 무슨 관계인지 잘 몰랐다. 하지만 위의 생각을 하고 나니, 분명 둘 간의 어떤 관계가 있긴 한 것 같은데 잘 와닿지는 않았다.
막연히 API가 좀 더 저수준이고 DSL이 여러 API를 조합해 사용하기 쉽게 만들어낸? 정도로 와닿았던 것 같다.
우선 훌륭한 API는 무엇인가 정리해보면 다음과 같다.
1. 코드를 읽는 독자가 어떤 일이 벌어질지 명확하게 이해할 수 있어야 한다.
=> 확실히 중요한 부분이라고 생각한다. 하지만 사용자가 API의 구현을 알지 않고 결과를 명확하게 예측할 수 있게 하려면 어떻게 해야 하는지는 좀 더 생각해봐야할 것 같다.
2. 코드에 불필요한 구문이나 번잡한 준비 코드가 가능한 한 적어야 한다.
=> 준비 코드가 적은만큼 사용자가 더욱 쉽고 가독성 좋게 사용할 수 있기 떄문에 이 부분도 1번의 연장선이라고 생각했다.
코틀린은 훌륭한 API를 작성하기 알맞다!
책에서는 특히 코틀린이 API를 훌륭하게 작성하기에 알맞다, 라고 다음의 이유들과 함께 소개하고 있다.
확장 함수, 중위 함수 호출, 람다, 연산자 오버로딩..
나 또한 이러한 이유들 덕분이라고 정확하게 인지하고 있지는 않았지만, 코틀린 언어가 다른 언어에 비해 자연어처럼 잘 읽힌다, 라고는 생각하고 있었던 것 같다.
DSL은 선언적이다.
DSL이 선언적이다는 것은 무슨 말일까? 책에서는 범용 프로그래밍 언어는 보통 명령적인 것에 반해 DSL은 선언적이라고 다음과 같은 예시와 함께 설명하고 있다.
명령적 언어는 어떤 연산을 완수하기 위해 필요한 각 단계를 순서대로 정확히 기술해야 하는 반면, 선언적 언어는 원하는 결과만 기술하면 알아서 다 해주는 느낌으로 생각됐다.
명령적 언어보다 선언적 언어가 더 효율적인 경우가 자주 있다.
나는 사실 이 부분이 처음에 잘 와닿지 않았다. 지금까지 알아본 특징으로는 명령적 언어가 좀 더 사용하기 번거롭고 선언적 언어가 더 사용하기 쉬운 느낌이었는데 그렇다면 자연히 좀 더 번거로운 만큼 성능적인 측면에서 더 효율적이지 않을까? 라는 생각이 들었다.
하지만 다음과 같은 이유로 위 같은 현상이 발생한다.
선언적 언어에서는 실행 엔진이 결과를 얻는 과정을 한꺼번에 최적화 할 수 있다.
이 말은 결국 명령적 언어에서는 각각의 작업에 대해 독립적으로 최적화를 해야한다는 말인데 사실 이것도 한번에 와닿지는 않았다. 명령적 언어도 일정 단위로 묶어서 최적화 엔진에 건네주면 되는 것 아닌가? 라고 생각했다.
그렇지만 사실 이는 다음과 같은 말이었다.
선언적 언어는 구체적인 실행 방안을 정하지 않으므로 세부 실행은 기술하지 않으므로 언어를 해석하는 엔진이 한꺼번에 최적화가 가능하다..
반면에 명령적 언어는 세부 실행을 거의 전달해주므로, 최적화에 제한이 있다는 것이었다.
선언적 언어를 해석하는 엔진을 구현하는 난이도가 상당하겠다는 생각이 들었다.
선언적 언어의 단점
하지만 선언적 언어 즉 DSL에는 이 모든 장점을 넘어서는 큰 단점이 있다고 한다..
바로 범용 언어로 만든 호스트 애플리케이션과 DSL을 함께 조합하기 어렵다는 것이다. DSL은 자체 문법이 있기 때문에 다른 언어의 프로그램 안에 직접 포함시킬 수가 없다고 한다.
따라서 이를 위해서는 DSL 프로그램을 별도의 파일이나 문자열 리터럴로 저장해야 한다고 한다. 사실 이 부분도 한번에 와닿지 않았다. 책에서 DSL의 예시로 정규 표현식도 들어줬었는데, 막상 정규 표현식을 사용할 때는 위같은 어려움을 딱히 느껴보지는 못했었다..고 정리하려는 순간 프리코스 때 마주했던 문제가 생각났다
바로 정규 표현식을 사용할 때 였는데 처음에는 개행 문자가 아닌 "\n" 문자 자체를 파싱하고 싶었기에 이스케이프 문자를 더해 "\\n"을 정규식에 포함하였으나, 이상하게도 자꾸 개행을 파싱하는 문제에 직면했던 적이 있었다. 알고보니 컴파일러를 거친 후에 Regex 엔진이 이를 보고 정규식 관련 로직을 수행하는 것이기 때문에, 이스케이프 문자를 두번 래핑하거나 """\n""" 등으로 raw string 명시를 해주었어야 했다..
본론으로 돌아와서 이러한 상황을 봤을 때 컴파일러는 정규식 즉 DSL을 이해할 수 없기 때문에 Regex 엔진에 이를 넘겨주어야한다는 사실을 깨닫게 되었고, 이는 어딘가에 기본 탑재되어있었기에 내가 위 어려움을 느끼지 못했다는 것을 알 수 있었다.
내부 DSL이란 무엇인가
우선 외부 DSL이란 독립적인 문법 구조를 갖는 DSL이다. 방금 언급했던 정규식이 외부 DSL 의 예라고 볼 수 있겠다.
반대로 내부 DSL은 범용 언어 즉 프로그램의 주 언어로 작성할 수 있는 DSL이다.
내부 DSL의 예시: 익스포즈드 프레임워크
코틀린에서는 코틀린으로 작성된 데이터 베이스 프레임워크인 익스포즈드(Exposed) 프레임워크가 있다.
SELECT Country.name, COUNT(Customer.id)
FROM Country
INNER JOIN Customer
ON Country.id = Customer.country_id
GROUP BY Country.name
ORDER BY COUNT(Customer.id) DESC
LIMIT 1
위 예제는 SQL을 이용한 구현이다. 내부 DSL인 코틀린의 익스포즈드를 사용하면 동일한 결과를 생성하는 프로그램을 만들 수 있다.
(Country innerJoin Customer)
.slice(Country.name, Count(Customer.id)
.selectAll()
.groupBy(Country.name)
.orderBy(Count(Customer.id), order = SortOrder.DESC)
.limit(1)
사실 두 예제에는 큰 차이가 있다. 바로 SQL을 이용한 구현은 SQL 쿼리가 돌려주는 결과를 다시 코틀린 객체로 변환해야 하지만, 내부 DSL로 구현한 프로그램에서는 자동으로 코틀린 객체를 반환해준다!
'Kotlin' 카테고리의 다른 글
| [우테코 8기 모바일 안드로이드] 레벨 0 4주차 회고 (0) | 2026.02.23 |
|---|---|
| Kotlin in action 2/e: 코루틴(1) 코루틴이란 무엇인가? (0) | 2026.02.23 |
| Kotlin in action 2/e: 어노테이션과 리플렉션(1) 코틀린의 어노테이션 (0) | 2026.02.20 |
| Kotlin in action 2/e: 고차함수: 람다를 파라미터와 반환값으로 사용(2) 인라인 함수, 코틀린 표준 라이브러리의 인라이닝 (0) | 2026.02.19 |
| Kotlin in action 2/e:고차함수: 람다를 파라미터와 반환값으로 사용(1) 고차함수, 스마트 스테핑, 함수 타입의 파라미터, 함수를 반환하는 함수 (1) | 2026.02.18 |