この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
モバイルメソッド10/12(金)に「つくってあそぼ Kotlin DSL」で発表させていただきました。
申し訳ないことに大変時間をオーバーし、資料もちょいちょいミスがありました。
11/2(金)に「つくってあそぼ Kotlin DSL 拡張編」を発表します。 リベンジに向けて頑張って資料つくっていきます。
資料
こちらは、少し修正してあるバージョンです。
補足
発表の中でわかりにくく説明してしまった部分を補足したいと思いします。
"name"{
be { name.isNotBlank() }
}
の説明が足りてませんでした。
この部分の難しさはthisが何になるかを見つけるところです。具体的には、beはどのクラスのメソッドで、{}内のレシーバーは何でしょうか?
いろいろと省略しているところがあるので、省略していない形まで戻して考えみましょう
"name"{
be { name.isNotBlank() }
//beのメンバーメソッドのthis(ChildValidation)を省略しない
this.be { name.isNotBlank() }
//ラムダのカッコを省略しない
this.be({ name.isNotBlank() })
//レシーバー指定のthis (T)を省略しない
this.be({ this.name.isNotBlank() })
}
"name"{}
は以下ようになっていましたね。
class Validation<T> {
operator fun String.invoke(init: ChildValidation<T>.() -> Unit)
}
つまり、レシーバーがChildValidationに指定されているので、{}内のthisはChildValidationになり、beはChildValidationのメソッドとわかります。
また、nameは、だれの持ち物で、誰をチェックしたいか、Userですね。つまりTです。{}はTをレシーバーとしたラムダということになります。
class Validation<T> {
operator fun String.invoke(init: ChildValidation<T>.() -> Unit)
}
class ChildValidation<T> {
fun be(f: T.() -> Boolean) {}
}
ソースコード全文
今回で作成したDSLのソースコード全部です。資料では全部俯瞰してみれなかったと思います。GitHubにあがっているのは拡張編まで実装済みですので、今回ものとかなり乖離してるかもしれません。
data class User(val name: String, val age: Int)
class Validation<T>(val validations: Map<String, ChildValidation<T>>) {
companion object {
inline operator fun <T> invoke(init: ValidationBuilder<T>.() -> Unit): Validation<T> {
val builder = ValidationBuilder<T>().apply(init)
return builder.build()
}
}
fun validate(value: T): Map<String, List<String>> {
val messages = mutableMapOf<String, List<String>>()
validations.forEach { map ->
val errors = map.value.validations
.asSequence()
.filter { !it.first.invoke(value) }
.map { it.second }
.filter { it.isNotEmpty() }
.toList()
if (errors.isNotEmpty()) {
messages.put(map.key, errors)
}
}
return messages
}
}
class ValidationBuilder<T> {
val validations = mutableMapOf<String, ChildValidation<T>>()
operator fun String.invoke(init: ChildValidation<T>.() -> Unit) {
validations.put(this, ChildValidation<T>().apply(init))
}
fun build(): Validation<T> {
return Validation(validations)
}
}
class ChildValidation<T> {
val validations = mutableListOf<Pair<T.() -> Boolean, String>>()
fun be(f: T.() -> Boolean) = f
infix fun (T.() -> Boolean).not(message: String) {
validations.add(Pair(this, message))
}
}
あとがき
前日のボドゲ会を開催し、勉強会の発表もあり充実した大阪の日々でした。
次も同じく11/1(木)にボドゲ会、11/2(金)勉強会発表しようと思いますので、両日とも宜しくお願いいたします!