Reputation: 319
When I comment out one line with // in this code, it doesn't work as expected.
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()
protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
println("$child passed to doInit.")
init(child)
children.add(child)
println("$child added")
}
override fun toString(): String {
println("toString called and ..now " +
"we have: <$name>${children.toString()}</$name>\"")
return "<$name>${children.toString()}</$name>"
}
}
fun table(init: TABLE.() -> Unit): TABLE {
println("table called")
return TABLE().apply(init)
}
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) {
println("tr called")
doInit(TR(), init);
println("after tr's doInit called")
}
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) {
println("td called")
doInit(TD(), init);
println("after td's doInit called")
}
}
class TD : Tag("td")
fun createTable() =
table {
tr {
td {
}
}
}
Even when I comment out init(child), fun createTable1() = table{tr{}}
works as expected. It calls doInit, and produces:
<table><tr></tr></table>
But fun createTable2() = table{tr{td{}}}
doesn't call doInit on td. It produces:
<table><tr></tr></table>
and not:
<table><tr><td></td></tr></table>
Thank you very much for reading.
Upvotes: 0
Views: 86
Reputation: 271440
We pass an instance of
TR
orTD
todoInit()
. Why do we need to create it one more time insidedoInit()
?
No, init(child)
does not create a new instance. It just calls init
, which is the second parameter of doInit
. Don't get put off by the word init
. It could be named f
or g
and you would still get the same result. It's just a function.
Here I've renamed some of the things. See if this helps:
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()
protected fun <T : Tag> applyAndAddAsChild(child: T, lambda: T.() -> Unit) {
lambda(child)
children.add(child)
println("doinit called")
}
override fun toString() =
"<$name>${children.joinToString("")}</$name>"
}
fun table(lambda: TABLE.() -> Unit): TABLE { println("table called"); return TABLE().apply(lambda)}
class TABLE : Tag("table") {
fun tr(lambda: TR.() -> Unit) { println("before doinit.tr called"); applyAndAddAsChild(TR(), lambda); println("tr called")}
}
class TR : Tag("tr") {
fun td(lambda: TD.() -> Unit) { println("before doinit.td called"); applyAndAddAsChild(TD(), lambda); println("td called")}
}
Anyway, you call init
by passing child
, the first parameter of doInit
, as an argument. As a side note, notice that the type of init
is T.() -> Unit
. This means that init
can also be called like this: child.init()
, which is arguably more natural.
What does init
do? Well, since it is a parameter, let's see what the callers of doInit
has passed to it!
// println calls removed for brevity
fun tr(init: TR.() -> Unit) { doInit(TR(), init) }
fun td(init: TD.() -> Unit) { doInit(TD(), init) }
So init
is actually the lambda arguments after tr
and td
!
In the case of
table { tr { td { } } }
You pass the lambda argument { td { } }
to tr
, so init
is td { }
. Now tr
executes, which calls doInit
, and if init(child)
is commented, init
won't be called, so td
won't be called, which means that doInit
for td
won't be called.
Commenting out init(child)
makes no difference in the case of
table { tr { } }
because the lambda argument for tr
is { }
, aka "do nothing". So no matter you comment out init(child)
or not, you do nothing.
It feels kind of weird to have a doInit
that takes a thing and another function, just to call the function with the thing as parameter. IMO, the code would look nicer if doInit
were declared like this:
protected fun <T : Tag> T.applyAndAddAsChild(init: T.() -> Unit) {
init()
[email protected](this)
}
Then the tr
and td
functions would have the same "shape" as table
:
// in "table" you can just apply the lambda, but in tr and td you have to
// add the new tag as a child too, which is the extra thing that
// applyAndAddAsChild does
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) = TR().applyAndAddAsChild(init)
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) = TD().applyAndAddAsChild(init)
}
fun table(init: TABLE.() -> Unit) = TABLE().apply(init)
Hopefully you see that there is a nice symmetry going on here.
Upvotes: 1