Hi guys, I want to ask some questions, my English ...
# functional
g
Hi guys, I want to ask some questions, my English is poor, but I will attach a code example. Thank you for reading this message. I try code of FP style in kotlin. I use SUM type (
sealed 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.
Copy code
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.
b
It's all about choosing the appropriate pattern for the task at hand. Will resources always have a "legal" state? Then the
checkLegal
method is probably better suited as an abstract method in the Resources class that each of them implements. Now, does it make sense for the Resource class to understand when it's in a legal state if you really want to know if it's legal within the context of something else? Then it might make more sense for it to be a pattern match or even another set of classes. One should never blindly follow one programming style or another, but use them as available tools in your toolset that you can apply when appropriate. However, only you can answer the questions about when it makes the most sense to use one style over the other.
g
@Brendan Campbell-hartzell Thank you for your answer. I don’t quite understand what you mean. I guess you want to tell me that if this function is only related to
Resource
and not related to other contexts, it is more suitable to use abstraction and implementation, otherwise the opposite. In fact, I just want to try to code with FP style, I am a novice in this regard, So I would like to know if there are any errors or optimizations in my code example, and whether it is FP style. Because I have to pattern-match the type in all functions related to
Resource
, which makes me very confused. I am looking forward to hearing from you.😄