kotlinadvanced

Type Class Pattern with Generics

Implement type class patterns in Kotlin: ad-hoc polymorphism, generic serializers, and extension-based dispatch.

kotlin
// Type class: Printable
interface Printable<T> {
    fun T.prettyPrint(): String
}

// Type class: Summable
interface Summable<T> {
    fun zero(): T
    fun add(a: T, b: T): T
}

// Type class: JsonSerializer
interface JsonSerializer<T> {
    fun serialize(value: T): String
    fun deserialize(json: String): T
}

// Instances for Int
object IntSummable : Summable<Int> {
    override fun zero() = 0
    override fun add(a: Int, b: Int) = a + b
}

object DoubleSummable : Summable<Double> {
    override fun zero() = 0.0
    override fun add(a: Double, b: Double) = a + b
}

object StringSummable : Summable<String> {
    override fun zero() = ""
    override fun add(a: String, b: String) = a + b
}

// Generic function using type class
fun <T> sumAll(items: List<T>, summable: Summable<T>): T =
    items.fold(summable.zero()) { acc, item -> summable.add(acc, item) }

// Printable instances
object IntPrintable : Printable<Int> {
    override fun Int.prettyPrint() = "Int($this)"
}

data class User(val name: String, val age: Int, val email: String)

object UserPrintable : Printable<User> {
    override fun User.prettyPrint() =
        "User { name=\"$name\", age=$age, email=\"$email\" }"
}

// JsonSerializer instances
object UserJsonSerializer : JsonSerializer<User> {
    override fun serialize(value: User): String =
        """{ "name": "${value.name}", "age": ${value.age}, "email": "${value.email}" }"""

    override fun deserialize(json: String): User {
        val nameMatch = Regex(""""name":\s*"([^"]+)"""").find(json)
        val ageMatch = Regex(""""age":\s*(\d+)""").find(json)
        val emailMatch = Regex(""""email":\s*"([^"]+)"""").find(json)
        return User(
            name = nameMatch?.groupValues?.get(1) ?: "",
            age = ageMatch?.groupValues?.get(1)?.toIntOrNull() ?: 0,
            email = emailMatch?.groupValues?.get(1) ?: ""
        )
    }
}

// Registry pattern
object TypeClassRegistry {
    private val summables = mutableMapOf<Class<*>, Summable<*>>()
    private val serializers = mutableMapOf<Class<*>, JsonSerializer<*>>()

    init {
        summables[Int::class.java] = IntSummable
        summables[Double::class.java] = DoubleSummable
        summables[String::class.java] = StringSummable
        serializers[User::class.java] = UserJsonSerializer
    }

    @Suppress("UNCHECKED_CAST")
    fun <T : Any> summable(klass: Class<T>): Summable<T>? =
        summables[klass] as? Summable<T>

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> summable(): Summable<T>? =
        summable(T::class.java)

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> serializer(): JsonSerializer<T>? =
        serializers[T::class.java] as? JsonSerializer<T>
}

// Generic operations using registry
inline fun <reified T : Any> List<T>.genericSum(): T? {
    val summable = TypeClassRegistry.summable<T>() ?: return null
    return fold(summable.zero()) { acc, item -> summable.add(acc, item) }
}

inline fun <reified T : Any> T.toJson(): String? =
    TypeClassRegistry.serializer<T>()?.serialize(this)

fun main() {
    // Summable
    println("--- Summable ---")
    println("Int sum: ${sumAll(listOf(1, 2, 3, 4, 5), IntSummable)}")
    println("Double sum: ${sumAll(listOf(1.5, 2.5, 3.0), DoubleSummable)}")
    println("String sum: ${sumAll(listOf("Hello", " ", "World"), StringSummable)}")

    // Using registry
    println("\n--- Registry ---")
    println("Generic int sum: ${listOf(10, 20, 30).genericSum()}")
    println("Generic double sum: ${listOf(1.1, 2.2, 3.3).genericSum()}")

    // Printable
    println("\n--- Printable ---")
    with(IntPrintable) { println(42.prettyPrint()) }
    with(UserPrintable) {
        println(User("Alice", 30, "alice@test.com").prettyPrint())
    }

    // Serializer
    println("\n--- Serializer ---")
    val user = User("Bob", 25, "bob@test.com")
    val json = user.toJson()
    println("JSON: $json")
    if (json != null) {
        val restored = UserJsonSerializer.deserialize(json)
        println("Restored: $restored")
        println("Equal: ${user == restored}")
    }
}

Use Cases

  • Ad-hoc polymorphism without inheritance
  • Generic serialization frameworks
  • Extensible type-driven dispatch

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.