This sounds like a very unreasonable request. but I can give a very reasonable usage scenario.
I’m creating a framework, its first sub module “kimmer” is nearly finished.
I bring immer(GitHub - immerjs/immer: Create the next immutable state by mutating the current one) for kotlin/jvm, it’s called kimmer.You can see the usage in this unit test.
“Node”, “BookStore”, “Book” and “Author” are defined by the user and are read-only data model types.
“NodeDraft”, “BookStoreDraft”, “BookDraft” and “AuthorDraft” are automatically generated by the pre-compiler(google/ksp) of this framework, so their source code can only be seen after compilation, they represent a mutable imperative data model.
In this unit test, we see the same effect as immer, create new immutable data model by modifying the mutable data model directly. The deeper the data model structure, the greater the value.
Let’s see an immutable interface
interface Book: Node {
// Readonly property, readonly list and readonly element
val authors: List<Author>
}
Let’ see the mutable interface generated by the ksp
inteface BookDraft<T: Book>: Book, NodeDraft<T> {
// Mutable property, mutable list and mutable element
override var authors: MutableList<AuthorDraft<T>>
// Get or create, remove the "unloaded" flag
fun authors(): MutableList<AuthorDraft<T>>
}
- Immutable interface “Book” uses “val” to declare readonly property, mutable interface “BookDraft” override it and use “var” to declare writable property.
- “Book.authors” uses readonly collection “kotlin.List”, “BookDraft” uses mutable collection “kotlin.MutableList”
- Element of “Book.authors” is readonly interface “Author”, element type of “BookDraft.authors” is mutable interface “AuthorDraft”
Now, let’s create/modify immutable object tree by modify mutable tree directly
val book = new(BookDraft.Sync::class) { // The type of the 'book' is Book, we get immutable data tree
name = "The book"
authors() += new(AuthorDraft.Sync::class) {
name = "Jim"
}
authors() += new(AuthorDraft.Sync::class) {
name = "Kate"
}
}
Or, we do it by another style
val book = new(BookDraft.Sync::class) { // The type of the 'book' is Book, we get immutable data tree
name = "The book"
authors = mutableListOf( // must be "mutableListOf", cannot be "listOf"
new(AuthorDraft.Sync::class) {
name = "Jim"
},
new(AuthorDraft.Sync::class) {
name = "Kate"
}
)
}
Here, developer must uses “mutableListOf”, “listOf” will cause compilation error.
Because the type of the getter of “BookDraft.authors” is “MutableList”, so its setter must be “MutableList” too.
However, for this scenario, readonly list can work fine (even if you use “Collections.unmodifableList” for runtime defense) and it makes more sense to the user.