Улучшение синтаксиса DSL

Чтобы начать изучение дизайна DSL с использованием функций языка Kotlin, у меня есть приведенная ниже попытка игрушечного DSL для создания групп членов с членами, имеющими имена. Я ищу указатели / подсказки по следующим

  1. Как мне избежать разделения групп точкой с запятой, если компилятор не дает точки с запятой

Groups.kt: 31: 45: ошибка: неразрешенная ссылка: member val grp = group {member {name ("Bob")} member {name ("Sandy")}}

  1. Могу ли я использовать лямбда для установки name вместо вызова функции?

  2. Могу ли я избежать необходимости name быть изменяемым в классе MEMBER?

Мой код

fun group(create: GROUP.() -> Unit) = GROUP().apply(create)

class GROUP {
    private val members = mutableSetOf<MEMBER>()

    fun member(create: MEMBER.() -> Unit) {
        val member = MEMBER()
        member.create()
        members.add(member)
    }

    override fun toString() = members.toString()

}

class MEMBER() {
    var name = ""
    set(value) {
        field = value
    }

    fun name(nameToSet: String) {
        name = nameToSet
    }
    override fun toString() = "MEMBER(" + name + ")"
}

fun main(args: Array<String>) {
    val grp = group { member { name ("Bob") }; member { name ("Sandy") } }
    println(grp)
}

В настоящее время вывод приведенного выше кода

[ЧЛЕН (Боб), ЧЛЕН (Сэнди)]


person Community    schedule 20.10.2018    source источник


Ответы (1)


Как избежать разделения групп точкой с запятой

Используя идиоматический формат, используя отдельные строки. В конце концов, весь смысл DSL состоит в том, чтобы сделать код очень читабельным, показывая иерархическую структуру, а выполнение всего в одной строке противоречит цели wole:

val grp = group { 
    member { 
        name ("Bob") 
    }
    member { 
        name ("Sandy") 
    } 
}

Могу ли я использовать лямбда для установки имени вместо вызова функции?

Было бы более логично и идиоматично удалить функцию имени и просто присвоить значение свойству:

name = "Bob"

Но да, вы также можете заменить свое имя function на

fun name(block: () -> String) {
    this.name = block()
}

и использовать

name {
    "Sandy"
}

Могу ли я избежать необходимости изменять имя в классе MEMBER?

Да: лямбда, переданная в функцию member (), будет настраивать дополнительный класс MemberBuilder, который будет изменяемым, но позволит создать неизменяемого MEMBER:

fun group(create: GROUP.() -> Unit) = GROUP().apply(create)

class GROUP {
    private val members = mutableSetOf<MEMBER>()

    fun member(configure: MemberBuilder.() -> Unit) {
        val memberBuilder = MemberBuilder()
        memberBuilder.configure()
        members.add(memberBuilder.build())
    }

    override fun toString() = members.toString()

}

class MEMBER(val name: String) {
    override fun toString() = "MEMBER($name)"
}

class MemberBuilder {
    var name = "";

    fun build() = MEMBER(name)
}

fun main(args: Array<String>) {
    val grp = group {
        member {
            name = "Bob"
        }
        member {
            name = "Sandy"
        }
    }
    println(grp)
}

Также обратите внимание, что классы по соглашению основаны на PascalCased, а не на ALL_CAPS.

person JB Nizet    schedule 20.10.2018