Как создать Kotlin DSL - DSL синтаксис Kotlin

С Анко вы можете написать функции обратного вызова, как это:

alert {
    title = ""
    message = ""
    yesButton {
       toast("Yes") 
    }
    noButton { 
       toast("No")
    }
}

как я могу создать вложенные функции, как это? Я попытался создать его, как показано ниже, но, похоже, не работает.

class Test {
    fun f1(function: () -> Unit) {}
    fun f2(function: () -> Unit) {}
}

теперь, если я использую это с функцией расширения,

fun Context.temp(function: Test.() -> Unit) {
    function.onSuccess() // doesn't work
}

вызов этого из Activity:

temp {
    onSuccess {
        toast("Hello")
    }
}

не работает. Мне все еще не хватает некоторых основных понятий. Может кто-нибудь здесь?

2 ответов


Котлин DSL-Языки

Котлин отлично подходит для написания собственных Доменные Языки, также называемый тип-безопасный строителей. Как вы упомянули, библиотека Anko является примером использования DSL. Самая важная языковая функция, которую вам нужно понять, называется "функциональные литералы с приемником", которым вы уже воспользовались:Test.() -> Unit

функциональные литералы с приемником-основы

Котлин поддерживает концепцию "литералов функций с приемниками". Это позволяет вызывать видимые методы на приемник литерала функции в его теле без каких-либо конкретных классификаторов. Это очень похожие на функции расширения, в котором также можно получить доступ к членам объекта receiver внутри расширения.

простой пример, также одна из самых крутых функций в стандартной библиотеке Котлина, isapply:

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

как вы можете видеть, такой литерал функции с receiver берется в качестве аргумента block здесь. Это block просто выполняется и приемник (который является экземпляром T) возвращается. В действии это выглядит следующим образом:

val text: String = StringBuilder("Hello ").apply {
            append("Kotliner")
            append("! ")
            append("How are you doing?")
        }.toString()

A StringBuilder используется в качестве приемника и apply вызывается на это. The block, передано как аргумент в {}(лямбда-выражение), не нужно использовать дополнительные квалификаторы и просто звонки append, видимый метод StringBuilder несколько раз.

функциональные литералы с приемником-в DSL

если вы посмотрите на этот пример, взятый из документации, вы видите это в действии:

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // create the receiver object
    html.init()        // pass the receiver object to the lambda
    return html
}


html {       // lambda with receiver begins here
    body()   // calling a method on the receiver object
}

на html() функция ожидает такой литерал функции с приемником с HTML как приемник. В теле функции вы можете увидеть, как она используется: экземпляр HTML создана и init вызывается на нем.

пользу

вызывающий такой функция более высокого порядка ожидание литерала функции с приемником (например,html()) вы можете использовать любой видимый HTML функция и свойство без дополнительных квалификаторов (например,this например), как вы можете видеть на вызов:

html {       // lambda with receiver begins here
    body()   // calling a method on the receiver object
}

Ваш Пример

я создал простой пример того, что вы хотели иметь:

class Context {
    fun onSuccess(function: OnSuccessAction.() -> Unit) {
        OnSuccessAction().function();
    }

    class OnSuccessAction {
        fun toast(s: String) {
            println("I'm successful <3: $s")
        }
    }
}

fun temp(function: Context.() -> Unit) {
    Context().function()
}

fun main(args: Array<String>) {
    temp {
        onSuccess {
            toast("Hello")
        }
    }
}

в вашем примере alert-это функция, возвращающая некоторый класс, например Alert. Также эта функция принимает в качестве литерала функции параметра с receiver

в вашем примере вы должны сделать свой onSuccess метод-член вашего тестового класса, и ваша функция temp должна возвращать экземпляр тестового класса без его вызова. Но чтобы тост вызывался, как в вашем желании, он должен быть функцией-членом любого класса, возвращаемого onSuccess

Я думаю, что вы не поймите, как именно работают функциональные литералы с receiver. Когда вам весело(что-то: A. () - > Unit), это означает, что это "что-то" является функцией-членом класса.

Так

вы можете посмотреть на моем блоге : как сделать небольшой DSL для AsyncTask