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

Как и в случае с anko, вы можете написать функции обратного вызова, например:

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 ответа

Решение

Котлин DSLs

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

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

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

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

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

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

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

StringBuilder используется в качестве приемника и apply вызывается на это. 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. Также эта функция принимает в качестве параметра литерал функции с приемником.

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

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

Так

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

Другие вопросы по тегам