Giovan
09/10/2021, 2:04 PMsealed class
) and pattern matching (when
) to enable functions to match different types to complete different tasks.
This makes most of my functions need to do a pattern matching on the type, which makes me feel a little clumsy, and if I need to add a new type, I need to add a case to each of these functions, it is easy to miss the new case.
The following is a fictitious code example, assuming it is a sticky note model.
sealed class Resource {
data class Note(val content: String?) : Resource()
data class Tag(val name: String?) : Resource()
data class Character(val name: String?) : Resource()
data class Address(val latitude: Float?, val longitude: Float?) : Resource()
}
fun pullResource(): List<Resource> = TODO()
fun Resource.toDecrypt(): Resource = when (this) {
is Resource.Note -> TODO()
is Resource.Tag -> TODO()
is Resource.Character -> TODO()
is Resource.Address -> TODO()
}
fun Resource.checkLegal(): Boolean = when (this) {
is Resource.Note -> content != null
is Resource.Tag -> name != null
is Resource.Character -> name != null
is Resource.Address -> latitude != null && longitude != null
}
inline fun <reified T> List<T>.insert() {
when (T::class) {
Resource.Note::class -> TODO("batch insert into the Note Entity")
Resource.Tag::class -> TODO("batch insert into the Tag Entity")
Resource.Character::class -> TODO("batch insert into the Character Entity")
Resource.Address::class -> TODO("batch insert into the Address Entity")
}
}
fun insertDB(l: List<Resource>) {
fun <T : Resource> List<Resource>.cast(): List<T> = this as List<T>
l.groupBy {
when (it) {
is Resource.Note -> "Note"
is Resource.Tag -> "Tag"
is Resource.Character -> "Character"
is Resource.Address -> "Address"
}
}.let {
it["Note"]?.cast<Resource.Note>()?.insert()
it["Tag"]?.cast<Resource.Tag>()?.insert()
it["Character"]?.cast<Resource.Character>()?.insert()
it["Address"]?.cast<Resource.Address>()?.insert()
}
}
fun main() {
pullResource().map(Resource::toDecrypt).filter(Resource::checkLegal).let(::insertDB)
}
I don’t know if this is right, it feel weird to me.
If in OO style, I thought maybe i can define an interface for Resource, and implement the interface when adding cases. The code is clean and does not miss new cases.julian
10/02/2021, 7:57 PMinsert
and its friends more generic. I hope the following will give you some ideas about further experiments you can try.
sealed interface Resource
interface InsertOp<T : Resource> {
fun insert(resources: List<T>): Resource
}
interface DecryptOp<T : Resource> {
fun decrypt(resource: Resource): Resource
}
interface ResourceOps<T : Resource> : InsertOp<T>, DecryptOp<T>
fun <T : Resource> List<T>.insert(insertable: InsertOp<T>): List<Resource> {
insertable.insert(this)
return this
}
fun <T : Resource> Resource.decrypt(decryptOp: DecryptOp<T>): Resource =
decryptOp.decrypt(this)
fun <T : Resource> ops(resource: T): ResourceOps<T> = TODO()
fun pullResource(): List<Resource> = TODO()
fun go() {
pullResource()
.map { Pair(it, ops(it)) }
.map { Pair(it.second.decrypt(it.first), it.second) }
.groupBy { it.second }
.forEach { entry ->
entry.key.insert(entry.value.map { it.first })
}
}
There's boilerplate in go
that could probably be eliminated with additional abstraction and refactoring.when
expressions handle all cases. And as of Kotlin 1.5.30
you can also opt in to require exhaustive when
statements.
https://kotlinlang.org/docs/whatsnew1530.html#exhaustive-when-statements-for-sealed-and-boolean-subjects