11. DSL 만들기
CS/코틀린 인 액션

11. DSL 만들기

11장에서 다루는 내용

- 영역 특화 언어 만들기

- 수신 객체 지정 람다 사용

- invoke 관례 사용

- 기존 코틀린 DSL 예제

 

확장함수를 사용해서 함수를 자연어처럼 쓰는건 멋지긴 한데... 잘 안쓸것 같다. 근데 이것이 진짜 코틀린처럼 쓰는거라며 언어 제작자가 미는 느낌.

나중에 쓰게 된다면 그때 자세히 보는걸로

 

 

11.1 API에서 DSL로

일반 구문 간결한 구문 사용한 언어 특성
StringUtil.capitalize(s) s.capitalize() 확장 함수
1.to("one") 1 to one 중위 호출
set.add(2) set += 2 연사자 오버로딩
map.get("key") map["key"] get 메서드에 대한 관례
file.use({ f -> f.read() }) file.use { it.read() } 람다를 괄호 밖으로 빼내는 관례
sb.append("yes")
sb.append("no")
with (ab) {
    append("yes")
    append("no")
}
수신 객체 지정 람다

SDL(Domain-Specific Language, 영역 특화 언어)은 평소에 아는 java같은 범용 프로그래밍 언어(general-purpose programming language)와는 다르게 하나의 역할만 하기 위해 존재한다. SQL 같은거. 그래서 그거에 특화되있음. 하지만 그렇기에 거기에 대한 안의 작동 방식은 몰라도 된다. 코틀린도 비슷하게 만들 수 있으며, 장점은 코틀린에서 구현하는 것이므로 일반 다른 변수 같은것들도 쉽게 넣을 수 있다. 외부 DSL로는 SQL을 사용하며, 내부 DSL로는 코틀린으로 작성된 데이터베이스 프레임워크인 익스포즈드(Exposed) 프레임워크가 제공하는 DSL을 사용한다.

SELECT Country.name, COUNT(Customer.id)
    FROM Country
    JOIN Customer
    ON Country.id = Customer.country_id
GROUP BY Country.name
ORDER BY COUNT(Customer.id) DESC
    LIMIT 1;
(Country join Customer)
    .select(Country.name, count(Customer.id))
    .selectAll()
    .groupBy(Country.name)
    .orderBy(count(Customer.id), Order.DESC)

kotlinx.html 라이브러리를 사용해서 html를 작성한 내용이다.

fun createAnotherTable() = createHTML().table {
    val numbers = mapOf(1 to "one", 2 to "two")
    for ((num, string) in numbers) {
        tr {
            td { +num.toString() }
            td { +string }
        }
    }
}

kotlin언어기 때문에 내부에 변수 작성 가능.

 

11.2 구조화된 API 구축: DSL에서 수신 객체 지정 DSL 사용

확장 함수 타입이라는 타입이라는게 있다. 놀랍게도 말장난이 아니다.

다음은 람다를 인자도 받는 buildString() 정의를 일반적으로 한 것과 수신 객체 지정 람다로 한 방법이다.

fun buildString(
    buildAction: (StringBuilder) -> Unit
): String {
    val sb = StringBuilder()
    buildAction(sb)
    return sb.toString()
}
val s = buildString {
    it.append("Hello, ")
    it.append("World!")
}

print(s) // Hello, World!
fun buildString(
    buildAction: StringBuilder.() -> Unit
): String {
    val sb = StringBuilder()
    buildAction(sb)
    return sb.toString()
}

val s = buildString {
    this.append("Hello, ")
    append("World!")
}

print(s) // Hello, World!

val appendExcl : StringBuilder.() -> Unit =
    { this.append("!") }
val stringBuilder = StringBuilder("Hi")
stringBuilder.appendExcl()
println(stringBuilder) // Hi!
println(buildString(appendExcl)) // !