JIGGAG

DSL이 대체 뭘까?

2021년 6월 10일
[참고 도서]
- 다재다능 코틀린 프로그래밍

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
  • 코틀린은 컴파일타임 함수 인젝션이 가능하다
  • Intdays 함수를 확장해서 사용할 수 있다

람다를 전달할 때 괄호는 필요없다

"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을 어떻게 어느 시점에 사용하게 될까?
    • 타입 세이프 빌더