[참고 도서]
- 다재다능 코틀린 프로그래밍
DSL이란?
- 도메인 특화 언어
- CSS, 정규표현식, XML, Gradle, React JSX 모두 DSL...
- DSL로 생산성을 높이고 어플리케이션 구현 시간과 에러를 줄여준다
- 해당 도메인에 특화된거니깐!
DSL의 타입과 특징
외부 DSL vs 내부 DSL
- 외부 DSL
- 해당 DSL만의 세상에서 펼쳐지는 엄청난 자유도
- 그러나 그들만의 세상을 내부적으로 가져오기 위한
파싱 작업
이 필요하다 - CSS, ANT 빌드파일...
- 내부 DSL
- 호스트 언어 컴파일러가 파싱해준다
- 그러나 좀 더 다양한 기능을 위해 호스트 언어의 역할을 뛰어넘는 노력이 필요?!
- Gradle 빌드파일
컨텍스트 주도와 유창성
- DSL은
context driven
이고fluent
이 높다- 공통된 컨텍스트를 공유하고 있기 때문에 커뮤니케이션이 간결하고 명확하다
- 그리고 같은 컨텍스트에 있기 때문에 암시적으로 범위가 적용되며 에러 가능성을 줄여준다
파라미터와 리시버
에 의해 컨텍스트를 관리한다
내부 DSL을 위한 코틀린
- 정적 타입인 코틀린이 DSL에서 한계를 뛰어넘기 위해 도와준 기능?!
생략 가능한 세미콜론
starts.at(14);
>>>
starts.at(14)
infix를 이용한 점과 괄호 제거
starts.at(14)
>>>
starts at 14
확장함수를 이용한 도메인 특화
2.days(ago)
>>>
2 days ago
- 코틀린은 컴파일타임 함수 인젝션이 가능하다
Int
에days
함수를 확장해서 사용할 수 있다
람다를 전달할 때 괄호는 필요없다
"test".meeting({
starts.at(14)
})
>>>
"test" meeting {
starts at 14
}
DSL 생성을 도와주는 암시적 리시버
- 람다 표현식에
암시적 리시버
를 전달한다리시버
는 코드의DSL 레이어 간에 컨텍스트 객체를 전달
할 수 있는 방법이다
- 람다 표현식을 실행할 때, 컨텍스트를 옮겨주는 암시적 리시버가 숨어있다!
- 암시적 리시버가 파라미터를 전달하거나 전역상태를 사용할 필요 없이
- 코드의 레이어 간에 프로세스를 진행 하기 위해 상태를 전달하는 것을 쉽게 만들어 준다
DSL을 돕기 위한 추가 특징
Any 클래스의 메소드(apply, let, run, also)
로 DSL의 표현력과 기능성을 올려준다- 람다를 실행시켜주고 암시적 리시버를 세팅해준다
this와 it
- 현재 객체를 참조하기 위해서
this
를 이용한다 - 람다 표현식의 단일 파라미터를 참조하기 위해
it
를 사용한다
- 현재 객체를 참조하기 위해서
타입 세이프 빌더
- 정적타입언어를 사용하면 컴파일시점에 코드를 검증할 수 있다
- 런타임에 발생하게 될 에러를 미리 방지할 수 있다
- 그러나 DSL로 허용되는 문법을 작성했는지 컴파일러가 확인할 수가 없다
- DSL
검증을 위해 타입 세이프 빌더
가 필요하다- 어노테이션을 사용해 컴파일러에게 속성이나 메소드의 스코프를 주시하도록 한다
스코프 제어를 통한 접근 제한
- 내부 DSL은 호스트 언어에 탑승하는 이득이 있다
- 파서를 따로 구현하지 않아도 된다
- 호스트 언어에서 할 수 있는
스코프 속성 접근이나 임의의 함수와 메소드 호출
은 DSL에서도 할 수 있다
- 오히려 이런 접근이 자유로운 것을 제한하고자
스코프 컨트롤 어노테이션
을 사용한다- DSL을 만들 때 현재 실행중인 암시적 리시버에만 접근이 가능하도록 제한하고 부모 리시버의 멤버에 접근하는 것을 제한해야 할 수 있다
@DSLMarker
라는 어노테이션을 사용해서 컴파일러가 중첩된 람다의 부모 리시버의 멤버에 암시적 접근을 거부하라고 요청 할 수 있다- js로 바꿔서 해보면 현재 객체에 없으면 부모로 올라가서 찾는 프로토타입 체인을 제한한다는 뜻?!
어노테이션으로 암시적 접근을 제한한 것이지 명시적으로 호출하는 것은 가능하다
결론
- 코틀린은 람다를 실행할 때 컨텍스트 객체를 할당할 수 있다는 점이 다른 정적 타입 언어로 DSL을 만들었을때 한계를 해소시켜준다
- 정적타입 언어이기 때문에 DSL을 사용할 때도 타입 안정성을 제공한다
- DSL 마커 어노테이션을 사용해서 자동으로 부모 리시버로 라우팅 되지 않고 현재 객체의 리시버로만 호출을 제한할 수 있다
🚨?!?!?
-
암시적 리시버
placeOrder { an item "연필" an item "지우개" complete { this with creditcard number "1234" } }
- 여기에
결제
와주문
컨텍스트가 존재한다는데... 어떻게 보는걸까?
- 여기에
-
infix + this + it
please add it to this cart now
- please.add(it to this.cart.now) 이건가..?
- please.add(it to this).cart(now) 이건가..?
- please.add(it to this).cart.now 이건가..?
🙈 더 알아보기
- DSL
- DSL을 어떻게 어느 시점에 사용하게 될까?
- 타입 세이프 빌더