I was wondering if it's possible to have immutable...
# getting-started
j
I was wondering if it's possible to have immutable lists/arrays inside a data class that's also itself immutable that's also a recursive DSL Something like this:
Copy code
@JsExport
@Serializable
@ExperimentalJsExport
@Suppress("ArrayInDataClass")
data class Field(

	@JsName("name")
	val type: String? = type,

	@JsName("name")
	val name: String? = type,

	@JsName("children")
	var children: Array<Field> = arrayOf(),  // <<<--- this var is bugging me

	@Transient
	val handler: (Field.() -> Unit)? = null,

) {

	init {
		handler?.invoke(this@Field)
	}

	fun addChild(field: Field): Field {
		return this.copy(
			children = arrayOf(*this.children, field)
		)
	}


	fun field(
		name: String? = null,
		handler: (Field.() -> Unit)? = null,
	): Field {
		return Field(
			name = name ?: "${this@Field.name}.$type",
			value = value,
			type = type,
		).let {
			this.children += it  // <<<--- this is the reason for the var children instead of val children
			handler?.invoke(it)
			it
		}
	}

}
The DSL when used is something like this (I've simplified it removing unimportant fields):
Copy code
val field = Field(
   name = "main",
) {
   field(
      name = "main",
   ) {
      field(type = "column") {
         field(type = "blah1")
      }
      field(type = "column") {
         field(type = "group") {
            field(type = "blah2.1")
            field(type = "blah2.2")
         }
         field(type = "group") {
            field(type = "blah2.3")
            field(type = "blah2.4")
         }
      }

   }
},
This field is to be used inside a MutableStateFlow, so whenever you add a child, it triggers the MutableStateFlow to emit to all Observers
Copy code
// adding a child programatically:
mutableStateFlow.getAndUpdate {
   it.copy(
      field = field.addChild(someOtherField)
   )
}
Only problem is, it's also possible to modify the original children in the Field class and if I make it immutable (val), then the DSL use-case seems tricky - it'll need an internal private list, but I can't override getters inside a data class to include the internal children and if I change the data class to a regular class, I lose the ability to copy. What is the clean way of getting this right assuming DSL and Immutability is doable?
e
https://kotlinlang.org/community/slackccugl.html "If the code you are pasting is longer than a few lines, use "Code or text snippet" available from + menu next to message input. Only first few lines will be displayed to all users and people interested in the code can expand your code to look into details."
or at least put long code blocks into a thread so that they don't hide all other content on the channel
in any case, for this you should keep your mutable and immutable types separate
there is a common pattern throughout many Kotlin libraries that goes like
Copy code
data class Field(
    val name: String,
    val children: List<Field>,
)

class FieldBuilder(val name: String) {
    val children: MutableList<Field> = mutableListOf()
}

inline fun Field(name: string, builderAction: FieldBuilder.() -> Unit): Field {
    val builder = FieldBuilder(name)
    builder.builderAction()
    return Field(builder.name, builder.children.toList())
}

inline fun FieldBuilder.field(name: String, builderAction: FieldBuilder.() -> Unit) {
    children.add(Field(name, builderAction))
}
j
Apologies for the long code dump, code snippets is for some reason no longer available in my Slack or the way to add them has changed - it has been a while since I used Slack. That pattern looks interesting, I guess the only thing different is that I'll need to use FieldBuilder(name = ...) { } instead of Field(name = ...) { }, which seems like an acceptable trade-off. Thanks for the help!
e
Slack has hidden snippets away in the "browse all shortcuts" menu by default, but you can still find it by name. or you can put the long code blocks in a thread.
any reason not to use
Field(...) { ... }
which works with an intermediary
FieldBuilder
? this is is fairly common in Kotlin, for example
Copy code
List(5) { it * it } // builder block runs with a MutableList receiver
Json { encodeDefaults = true } // builder block runs with a JsonBuilder receiver
etc.
j
I'll delete this post in a bit, doesn't seem like I can add snippets afterwards. FieldBuilder.() : yes, I was thinking that too, that'll give me 100% what I was looking for!
ah, I see now, that inline fun Field will give me that sneaky to use an uppercase function name that appears as a constructor
e
https://kotlinlang.org/docs/coding-conventions.html#function-names "factory functions used to create instances of classes can have the same name as the abstract return type"
K 1
💪 1
I wouldn't delete the post unless mods ask you to, but keep it in mind for the future
👍 1