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

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

Kotlin DSLs

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

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

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

Простым примером является также одна из самых больших функций в стандартной библиотеке Котлин.

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

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

 StringBuilder("Hello ").apply { append("Kotliner") append("! ") append("How are you doing?") }.toString()) 

Мы используем StringBuilder поскольку приемник и invoke 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") } } } 

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

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

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

Так

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