kotlinadvanced

Builder Inference and Generic DSLs

Use @BuilderInference for type-safe DSL builders with generic type inference and collection construction.

kotlin
// buildList with builder inference
fun main() {
    // buildList — type is inferred from usage
    val numbers = buildList {
        add(1)
        add(2)
        addAll(listOf(3, 4, 5))
        // Type is List<Int> — inferred
    }
    println("Numbers: $numbers")

    // buildMap
    val config = buildMap {
        put("host", "localhost")
        put("port", "8080")
        put("debug", "true")
    }
    println("Config: $config")

    // buildSet
    val unique = buildSet {
        addAll(1..5)
        addAll(3..7)
    }
    println("Unique: $unique")

    // buildString
    val html = buildString {
        appendLine("<!DOCTYPE html>")
        appendLine("<html>")
        appendLine("  <body>")
        appendLine("    <h1>Hello</h1>")
        appendLine("  </body>")
        appendLine("</html>")
    }
    println(html)

    // Custom builder with @BuilderInference
    class Pipeline<T> {
        private val steps = mutableListOf<(T) -> T>()

        fun step(transform: (T) -> T) {
            steps.add(transform)
        }

        fun execute(input: T): T =
            steps.fold(input) { acc, step -> step(acc) }
    }

    fun <T> pipeline(block: Pipeline<T>.() -> Unit): Pipeline<T> =
        Pipeline<T>().apply(block)

    val stringPipeline = pipeline<String> {
        step { it.trim() }
        step { it.lowercase() }
        step { it.replace(" ", "-") }
    }
    println("Pipeline: ${stringPipeline.execute("  Hello World  ")}")

    val intPipeline = pipeline<Int> {
        step { it * 2 }
        step { it + 10 }
        step { it * it }
    }
    println("Int pipeline: ${intPipeline.execute(5)}")

    // Schema builder with inference
    class Schema<T> {
        val fields = mutableListOf<Field<T, *>>()

        fun <V> field(
            name: String,
            getter: (T) -> V,
            validator: ((V) -> Boolean)? = null
        ) {
            fields.add(Field(name, getter, validator))
        }

        fun validate(item: T): List<String> {
            return fields.mapNotNull { field ->
                @Suppress("UNCHECKED_CAST")
                val f = field as Field<T, Any?>
                val value = f.getter(item)
                if (f.validator != null && !f.validator.invoke(value)) {
                    "${f.name}: validation failed (value=$value)"
                } else null
            }
        }
    }

    data class Field<T, V>(
        val name: String,
        val getter: (T) -> V,
        val validator: ((V) -> Boolean)?
    )

    fun <T> schema(block: Schema<T>.() -> Unit) = Schema<T>().apply(block)

    data class Product(val name: String, val price: Double, val stock: Int)

    val productSchema = schema<Product> {
        field("name", { it.name }) { it.isNotBlank() }
        field("price", { it.price }) { it > 0 }
        field("stock", { it.stock }) { it >= 0 }
    }

    val valid = Product("Laptop", 999.0, 10)
    val invalid = Product("", -5.0, -1)

    println("\nValid: ${productSchema.validate(valid)}")
    println("Invalid: ${productSchema.validate(invalid)}")

    // Sequence builder
    val primes = sequence {
        yield(2)
        var candidate = 3
        val found = mutableListOf(2)
        while (true) {
            if (found.none { candidate % it == 0 }) {
                yield(candidate)
                found.add(candidate)
            }
            candidate += 2
        }
    }
    println("\nFirst 15 primes: ${primes.take(15).toList()}")

    // Result builder pattern
    sealed class Result<out T> {
        data class Ok<T>(val value: T) : Result<T>()
        data class Err(val message: String) : Result<Nothing>()
    }

    fun <T> resultOf(block: () -> T): Result<T> = try {
        Result.Ok(block())
    } catch (e: Exception) {
        Result.Err(e.message ?: "Unknown")
    }

    val r1 = resultOf { 42 / 2 }
    val r2 = resultOf { 42 / 0 }
    println("\nResult 1: $r1")
    println("Result 2: $r2")
}

Use Cases

  • Type-safe collection construction
  • Generic pipeline and transformation builders
  • Schema validation with inferred types

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.