본문 바로가기

프로그래밍 고찰/고찰

[고찰] 나는 왜 Interal DSL를 이해하기 그토록 어려웠는가? [Feat. 마틴 파울러]

DSL 에 대한 내 이해가 틀릴 수 있음!
이 글은 정답이 아님!

 

DSL 이 뭔지는 생략하고

외부 DSL 은 나에게 명확했다. SQL을 통한 DB 조작 , XML, JSON 을 통한 설정파일, 또는 사용자가 정의한 text 파일을 파싱해서 컨트롤하는것.

 

내부 DSL 은 이해가 되려고하면 안되고, 안되려고 하면 되고... 하던 느낌이었다.

왜 방해가 되었는가? 하면 크게 2가지 개념이 충돌해서 였다.

  1. kotlin / Scala / Lisp(Clojure) 에서 DSL 을 구축하기 위해 쓰는 테크닉
  2. C# / JAVA 에서 Fluent API 를 DSL 이라고 함

"DSL" 이라는것을 구축하기 위해서, 또는 이해하기 위해서 공부하거나, 또는 "모나드" (특히 Free Monad) 등을 공부할때, 또는 Lisp 계열을 사용할때,

"DSL" 을 구축하기위함! 이라는 말을 많이 듣는다.

그래서 내가 이런것을 공부하면서 DSL 이라고 느낀것은 다음과 같다.

1. Lisp / Monad 와 같이 데이터 (혹은 ADT 같은 데이터의 구조 자체) 를 "메타 프로그래밍" 적인 요소를 활용하여 새로운 문법으로써 활용하는것 (Haskel 의 Do notation, F# 의 Compute Expression, Clojure 의 메크로, Data-Driven 한 라이브러리, Free 모나드)

2. 언어에서 제공해주는 "구문" 처럼 보일수 있도록 하는 마법의 요소들을 활용 ( infix 연산자 지원, () 대신 {} 구문 등)

 

1번의 예시는 명확했다. 일단 데이터를 해석할 새로운 인터프리터를 만드니까 (또는 데이터구조 자체를 메크로/모나드를 이용해서 인터프리팅하니까)

그리고 이런쪽 언어(주로 함수형) 에서는 이런 행위를 할때 DSL 이나 Mini Lang 이라는 표현을 굉장히 좋아한다.

 

또는 2번처럼 (주로 Kotlin 이나 Scala) 도 어느정도 이해가 됬다.

일단 눈에 보이는것만 똑 때놓고 보면 Kotlin 이나 Scala 문법으로는 전혀 보이지 않으니까.

 

근데 Fluent API 는 도저히 이해가 되지않았다. Fluent API 를 포함해서, 그냥 해당 언어의 문법 (Record Initial 문법을 보고 DSL 이라고 하던지.. 그냥 |> 파이프라인으로 함수 연결해둔걸 Mini 언어라고 표현하는것들...)

아니 그냥 누가봐도 C# 코드인데?
아니 그냥 F# 함수 쓴건데?
아니 그냥 Clojure 함수 여러번 호출한게 DSL 이라고?

나같은 사람이 더있나 인터넷으로 이러한 구분을 계속 찾아보았지만 명확한 답을 내리지는 못했다.

주로 "Internal DSL 은 잘만들어진 API 처럼 보인다." 라던가 "주로 Ruby, Lisp 같은데서 많이 활용한다" 이런 내용들의 반복이었는데, 이걸 봐도 이해가 안갔다.

아니 걍 API 는 다 이렇게짜지 뭐 다르게 짤수가 있나?
아니 그럼 그 언어지 그걸왜 DSL 이라고 불르는겨?
내가 이해 안 가는건 C#/Java 쪽에서 Fluent API 를 DSL 이라고 부르는건데..

뭐 이런거였고,

DSL 의 아부지 마틴파울러씨의 블로그까지 몇번 읽어봤는데도 뭔가... 애매했다.

그래서 그냥 삿다

DSL 고객과 함께 하는 도메인 특화 언어 (마틴 파울러)

 

모던한 언어에 대한 예제가 많이 부족하긴한데 (만들어진거 자체가 오래되었다. 주로 옛버전 Java 가 예시)

이 책을 읽고 대충 감을 잡게 되었다.

 

라이브러리를 설계하는 일은 사실 언어를 설계하는 일이다 (벨 연구소 격언)
DSL 을 사용하는 방식을 흔히 '선언적 프로그래밍' 이라고 부르기도 한다.
내부 DSL 을 만들 때 Lisp가 왜 그토록 매력적인 언어인지,...

 

그렇다. 나는 DSL 을 이해하기 위한 방향 자체가 꺼꾸로였다.

나는 FP 선호자로써, "선언적 프로그래밍" 이라는것을 먼저 접하고, 당연한것으로 생각하고있었다. 

FP 언어에서는 인터페이스를 선언적으로 쓸수있게 제공하는게 일반적이다.

Clojure / F# 등의 문법을 아는 상태에서 Internal DSL 구축에 대하여 처음 접하고, 그 이후 Clojure Macro/ Free Monad / 메타프로그래밍 ... 등을 찾아봣고, 그걸 기반으로 DSL 에 대하여 생각했기 때문에 "언어 문법을 만드는것" 에만 초점을 맞추게 되었다.

그런데 그게 아니라 방향 자체가 잘못된것이다. 라이브러리나 프레임워크등을
도메인 Expert 나 우리같은 일반 프로그래머에게 조차도 사용하기 쉽게 (Fluent 하게) 제공해주는것 자체,
제한된 상황 내에서 고민과 노력으로 이루어진 "깔끔한 문장" 으로 우리에게 제공하는 API 자체가 DSL 으로 볼수있는것이다.

내가 C# 의 Fluent API 를 보고 왜 DSL 이라고 생각하지 못했는가? 나는 그걸 당연하게 여기고있기 때문이다. (그것 이상의 "깔끔한 문장" 을 제공하는 Feature 를 사용하던 사람이므로)

더 나은 구조는 함수에서 이름으로 접근하는 파라미터 (named parameter) 를 가진 경우다. 예를 들어...

DSL 서적을 보면 이러한 느낌의 글귀가 많이 나온다.

"~~~" 이 지원되면 좋은데, 지원되는 언어가 별로 없고, 지원이 안되면 ~~~ 식인 방법으로 사용해야한다

즉 나는 이미 더 "깔끔한 문장" 을 제공하기 위한 언어적 Feature 가 풍부한 언어를 사용해왔으므로, 이해하지 못하고있던것이다.

 

해당 책의 시작부문에, 그냥 만들어져있는 API (마틴파울러씨의 표현으로는 커멘트-쿼리 API)를 

XML 설정으로 사용하는 예시 -> 내맘대로 DSL 예시 -> Ruby 예시 -> Java 예시 를 차례대로 보여준다.

아주 간단하게 발췌 하여 예시를 들자면 다음과 같다.

<state name="idle">
	<action command="unlockDoor"/>
    <action command="lockPanel"/>
</state>

이건 누가봐도 XML DSL 이다. 누가봐도 외부 DSL 이다. (대충 state.xml 이라고 하자)

그런데 이 XML 은 보기 불편하므로 내가 원하는 대로 문법을 작성해보자. 

state idle
	action {unlockDoor lockPanel}
end

난 이런식으로 썻으면 좋겠다, 이름은 흠..  대충 state.myml 이라고하자

이것은 누가봐도 DSL 이다. 대신 이 구문을 state.myml 이라고 저장하고, 이를 파싱하기 위한 파서를 쓰면된다.

파서는 물론? 내가짜야된다 이건 그냥 string 집합체 이기 때문이다.

 

그러나 이 파서를 내가 쓰는 언어내에서 그대로 쓸수있다면? 심지어 조금의 설정코드를 추가하면 이걸 내가 쓰는 언어 의 파서가 알아서 구문분석까지 뚝딱해준다면?

그럼 이건 internal DSL 이라고 볼수있을것이다.

 

그러나 현실적으로 힘들다. 그러면 어떻게 해야하느냐? 타협을 해야한다.
내가 쓰는 언어 내에서 그대로 사용할 수 있는게 베스트지만, 파써를 직접 한땀 한땀 구현하는것보다야 타협하고 적당한  DSL 을 쓰는게 좋을것이다.

state :idle do
	action :unlockDoor :lockPanel
end

이건 루비 문법이다. 그러나 루비 문법이지만, 아까 state.myml 과 큰 차이가 없다.

이게 가능한 이유는 ruby 에서는 이렇게 쓸수있도록 도와주는 문법이 풍부하니까!

그리고 그냥 ruby 코드에 불과하기 때문에 그냥 그냥 루비코드에 때려박아도 된다.

 

비록 내가 그대로 원하던 myml 은 아니지만 충분히 리즈너블하고, 리더블 하다

 

이걸 우리는 Interal DSL 을 구축했다고 말할수있다.

 

그럼 자바에서는

State idle;
Events unlockDoor,lockPanel;

idle
  .action (unlockDoor,lockPanel)
    ;

 

많이... 다르다. 그냥 누가봐도 자바코드다 아무리봐도.

하지만 어쩔수없다, 비록 내가 원하던 문장 자체를 쓸수있는것은 아니지만 타협할수있다.

Java 에서는 이렇게 쓸수있도록 도와주는 문법이 풍부하지 않으니까!

그러나 어쩔수없다. 없는건 없는거고, 나는 내가 가능한 한도내에서 Fluent 하게 표현할뿐이다.

따라서 이것도 DSL 이라고 할 수 있다.

Ruby 코드가 이쁘고 Java 코드는 별로 안이쁘다고 Java 는 DSL 이 아니라고 할수는 없는것 아닌가?

 

그동안 내가 알던 메타 프로그래밍, Free DSL, Infix 오퍼레이터, 네임드 파리미터, Initalize 구문들 등등등.. 이것은 단순히 더 풍부하게 표현하기 위한 Feature 일 뿐이다. 그게 부족한 언어에서는 못쓸뿐이고, 쓸 수 있는 언어에서는 쓰면 좋은것 뿐이다.

 

즉 지금껏 내가 알고있던 

X 언어 내에서 코드를 이곳저곳 조작해서 새로운 언어 문법을 만들어서 사용하기 쉽게 만든 언어 (기존 언어와 달라야함)

이게 아니라

X 언어 내에서 코드를 이곳저곳 조작해서 새로운 언어 문법을 만들어서 사용하기 쉽게 만든 언어(기존 언어와 달라야함)

이것에 포커스를 맞춰야 하는것.

728x90