Подтвердить что ты не робот

Как создать синтаксис 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
}

Вызов этого из действия:

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

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

4b9b3361

Ответ 1

DSLs Kotlin

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

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

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

Простым примером, также являющимся одной из самых крутых функций в стандартной библиотеке 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()

A StringBuilder используется как приемник, и на него вызывается apply. block, переданный как аргумент в {} (выражение lambda), не нужно использовать дополнительные квалификаторы и просто вызывает 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")
        }
    }
}

Ответ 2

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

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

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

Итак,

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