[Kotlin] DSL

 πŸ’‘ 도메인 νŠΉν™” 언어인 DSL의 κ°œλ…μ— λŒ€ν•˜μ—¬ μ•Œκ³ , κ°„λ‹¨ν•œ μ‹€μŠ΅μ„ μ§„ν–‰ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

DSL?

  • νŠΉμ • μ—…λ¬΄λ‚˜ 문제 해결을 μœ„ν•΄ μ΅œμ ν™”λœ μ–Έμ–΄λ‘œ, ν•΄λ‹Ή λ„λ©”μΈμ—μ„œ 일반적으둜 μ‚¬μš©λ˜λŠ” κ°œλ…κ³Ό 연산을 κ°„κ²°ν•˜κ³  μ§κ΄€μ μœΌλ‘œ ν‘œν˜„ν•  수 있게 ν•΄μ€λ‹ˆλ‹€.
  • Domain Specific Language의 μ•½μžλ‘œ, νŠΉμ • 도메인에 κ΅­ν•œν•΄ μ‚¬μš©ν•˜λŠ” μ–Έμ–΄μž…λ‹ˆλ‹€.
  • λ°˜λŒ€ κ°œλ…μœΌλ‘œλŠ” General Purpose Languageκ°€ 있으며, C, C++, Kotlin, Swift λ“±μ˜ ν”„λ‘œκ·Έλž˜λ° 언어듀이 이에 ν•΄λ‹Ήν•©λ‹ˆλ‹€.

DSL vs GPL

  • DSL
    • μ›Ή νŽ˜μ΄μ§€ λ ˆμ΄μ•„μ›ƒ, λ°μ΄ν„°λ² μ΄μŠ€ 쿼리, λΉŒλ“œ μžλ™ν™” λ“± μž‘μ—…μ„ μ‰½κ²Œ ν•˜κΈ° μœ„ν•΄ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    • 생산성과 가독성이 μ’‹κ³ , λ³΅μž‘ν•œ μž‘μ—…μ„ κ°„λ‹¨ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν•˜μ§€λ§Œ λ²”μš©μ μ΄μ§€ μ•Šμ•„μ„œ λ‹€λ₯Έ 도메인에 μ μš©ν•˜κΈ° μ–΄λ ΅κ³ , 도메인 μ™Έμ˜ μž‘μ—…μ—λŠ” μ ν•©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
    • ν•™μŠ΅ 곑선이 μžˆμŠ΅λ‹ˆλ‹€.
    • ν˜„μ‹€μ„Έκ³„μ™€ λΉ„κ΅ν•˜μžλ©΄ λͺ©μˆ˜μ˜ 톱과 망치, μžλ™μ°¨ μ •λΉ„μ‚¬μ˜ 렌치 μ„ΈνŠΈ λ“± νŠΉμ • μž‘μ—…μ˜ νš¨μœ¨μ„ κ·ΉλŒ€ν™”ν•œ μž₯비듀이 μžˆμŠ΅λ‹ˆλ‹€.
  • GPL
    • λ‹€μ–‘ν•œ 도메인과 문제 μ˜μ—­μ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ„€κ³„λœ λ²”μš© ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μž…λ‹ˆλ‹€.
    • λ‹€μ–‘ν•œ 문제 μ˜μ—­μ—μ„œ μ‚¬μš© κ°€λŠ₯ν•˜λ©°, μžμ›μ΄ ν’λΆ€ν•˜κ³  λΌμ΄λΈŒλŸ¬λ¦¬μ™€ ν”„λ ˆμž„μ›Œν¬κ°€ λ§ŽμŠ΅λ‹ˆλ‹€.
    • ν•˜μ§€λ§Œ νŠΉμ • 도메인에 μ΅œμ ν™”λ˜μ§€ μ•Šμ•„μ„œ λ³΅μž‘ν•œ 도메인 μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” 데 λΉ„νš¨μœ¨μ μΌ 수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν˜„μ‹€μ„Έκ³„μ™€ λΉ„κ΅ν•˜μžλ©΄, λ‹€μš©λ„ 곡ꡬ μ„ΈνŠΈ λ“± λ‹€μ–‘ν•œ μ§κ΅°μ—μ„œ νŽΈλ¦¬ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλŠ” μž₯비듀이 μžˆμŠ΅λ‹ˆλ‹€.

Kotlinκ³Ό DSL

  • Kotlin으둜 μ•ˆλ“œλ‘œμ΄λ“œ κ°œλ°œν•˜λ©΄μ„œ 자주 DSL을 μ ‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λŒ€ν‘œμ μœΌλ‘œ λΉŒλ“œ 슀크립트 μ•ˆμ—μ„œ μ•ˆλ“œλ‘œμ΄λ“œ κ΄€λ ¨ν•œ μ˜΅μ…˜μ„ λͺ…μ‹œν•  λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.
buildFeatures {
		viewBinding = true
}
  • λΉŒλ”λ‚˜ νŒ©ν† λ¦¬λ₯Ό μ΄μš©ν•˜μ—¬ ν•„μš”ν•œ μ˜΅μ…˜μ„ λͺ…μ‹œν•˜κ³  객체λ₯Ό μƒμ„±ν•˜λŠ” κ΅¬ν˜„ νŒ¨ν„΄κ³Ό λΉ„κ΅ν•˜μ—¬ 더 κ°„λž΅ν•˜κ³  읽기 μ‰½κ²Œ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 라이브러리λ₯Ό κ°œλ°œν•  λ•Œ DSL을 적절히 ν™œμš©ν•˜μ—¬ μ œκ³΅ν•˜λ©΄, 라이브러리 μ‚¬μš©μžλŠ” 보닀 쉽고 κ°„κ²°ν•˜κ²Œ 호좜 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 잘 μ •λ¦¬λœ DSL은 μƒλŒ€μ μœΌλ‘œ μžμ—°μ–΄μ— κ°€κΉŒμ›Œ 가독성도 λ†’μŠ΅λ‹ˆλ‹€ !

Kotlin의 κΈ°λŠ₯

  • Kotlin이 DSLκ³Ό 잘 μ–΄μšΈλ¦¬λŠ” μ΄μœ λŠ” Kotlinμ—μ„œ μ œκ³΅ν•˜λŠ” μ•„λž˜μ™€ 같은 κΈ°λŠ₯λ“€κ³Ό 관련이 μžˆμŠ΅λ‹ˆλ‹€.
  • λžŒλ‹€ ν‘œν˜„μ‹ (Lambda Expression)
    • μ–΄λ–€ ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°μ™€ λ°˜ν™˜κ°’μœΌλ‘œλ§Œ λ‚˜νƒ€λ‚Έ ν‘œν˜„μ‹μž…λ‹ˆλ‹€.
(Int, Int) -> Unit
  • κ³ κ³„ν•¨μˆ˜ (Higher-order Function)
    • μ–΄λ–€ ν•¨μˆ˜κ°€ ν•˜λ‚˜ μ΄μƒμ˜ ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°λ‘œ κ°–κ±°λ‚˜, ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 경우λ₯Ό κ³ κ³„ν•¨μˆ˜λΌκ³  ν•©λ‹ˆλ‹€.
fun calculate(a: Int, b: Int, operation: (Int,Int) -> Int): Int {
		return operation(a,b)
}
  • ν™•μž₯ν•¨μˆ˜ (Extension Function)
    • 이미 μ •μ˜λœ ν΄λž˜μŠ€λ‚˜ μΈν„°νŽ˜μ΄μŠ€μ— 상속 없이 ν•¨μˆ˜λ₯Ό μΆ”κ°€ μ •μ˜ν•  수 μžˆλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.
fun Calcualtor.newFunction() { .. }

HTML DSL 적용

  • HTML의 TAGλ₯Ό DSL둜 κ΅¬ν˜„ν•΄λ΄€μŠ΅λ‹ˆλ‹€.
class Tag(val name: String) {
    private val children = mutableListOf<Tag>()
    private val attributes = mutableMapOf<String, String>()

    fun setAttribute(name: String, value: String) {
        attributes[name] = value
    }

    override fun toString(): String {
        val attrs = if (attributes.isEmpty()) "" else attributes.map { "${it.key}=\\"${it.value}\\"" }.joinToString(" ", " ")
        val childrenStr = children.joinToString("")
        return "<$name$attrs>$childrenStr</$name>"
    }

    fun tag(name: String, init: Tag.() -> Unit) {
        val tag = Tag(name)
        tag.init()
        children.add(tag)
    }
}

fun html(init: Tag.() -> Unit): Tag {
    val html = Tag("html")
    html.init()
    return html
}
  • HTML νƒœκ·Έλ₯Ό λ‹΄λ‹Ήν•˜λŠ” Tag와 μ‹€μ œ DSL의 ν™•μž₯ ν•¨μˆ˜κ°€ 될 html을 μ„ μ–Έν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • κ·Έ ν›„ html에 μ‘΄μž¬ν•˜λŠ” head와 bodyλ₯Ό ν™•μž₯ν•¨μˆ˜λ₯Ό 톡해 μ„ μ–Έν•˜μ—¬μŠ΅λ‹ˆλ‹€.
  • title, p, a 등을 ν™•μž₯ ν•¨μˆ˜λ‘œ μ„ μ–Έν•˜μ—¬ κ°„κ²°ν•˜κ²Œ μΆ”κ°€ν•  수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
fun Tag.head(init: Tag.() -> Unit) = tag("head", init)
fun Tag.body(init: Tag.() -> Unit) = tag("body", init)
fun Tag.title(text: String) = tag("title") { tag("text") { setAttribute("text", text) } }
fun Tag.p(text: String) = tag("p") { tag("text") { setAttribute("text", text) } }
fun Tag.a(href: String, text: String) = tag("a") {
    setAttribute("href", href)
    tag("text") { setAttribute("text", text) }
}
  • ν™•μž₯ ν•¨μˆ˜, 고계 ν•¨μˆ˜, λžŒλ‹€ ν•¨μˆ˜λ₯Ό μ΄μš©ν•œ DSLλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
fun main() {
    val htmlDocument = html {
        head {
            title("My Page")
        }
        body {
            p("Hello, World!")
            a(href = "<https://example.com>", text = "Visit Example")
        }
    }

    println(htmlDocument)
}
  • html 블둝 μ•ˆμ—μ„œ head와 bodyλ₯Ό μ •μ˜ν•˜κ³ , 각각의 블둝 μ•ˆμ—μ„œ νƒœκ·Έλ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • νƒœκ·Έλ₯Ό μΆ”κ°€ν•  λ•Œ λžŒλ‹€ ν‘œν˜„μ‹μ„ μ‚¬μš©ν•˜μ—¬ νƒœκ·Έ λ‚΄λΆ€λ₯Ό μ •μ˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • 이둜 μΈν•΄μ„œ κ°„νŽΈν•˜κ²Œ DSLλ₯Ό μ‚¬μš©ν•  수 있게 λ˜μ—ˆμœΌλ©°, 생산성과 가독성이 높은 μ½”λ“œλ‘œ λ³΅μž‘ν•œ μž‘μ—…μ„ κ°„λ‹¨ν•˜κ²Œ ν‘œν˜„ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ°Έκ³ 

https://myungpyo.medium.com/kotlin-dsl-κ°„λ‹¨νžˆ-μ•Œμ•„λ³΄κΈ°-5f95fddf00f9

https://velog.io/@akimcse/Kotlin-in-Action-11.-DSL-λ§Œλ“€κΈ°

https://kotlinlang.org/docs/lambdas.html