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.julian
10/03/2021, 1:01 AMwhen
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