Chào đón tất cả các độc giả trên Cafedev! Hôm nay, chúng ta sẽ đào sâu vào thế giới hấp dẫn của Kotlin và khám phá cách sử dụng builders với tính năng suy luận kiểu builder. Điều này không chỉ giúp tối ưu hóa mã nguồn mà còn mở ra những cơ hội mới trong quá trình phát triển. Hãy cùng Cafedev tìm hiểu cách Kotlin kết hợp với builders và suy luận kiểu để mang lại trải nghiệm lập trình mạnh mẽ và linh hoạt. Đừng bỏ lỡ cơ hội đàm phán kiến thức mới và chia sẻ ý kiến tại Cafedev ngay hôm nay!

Kotlin hỗ trợ suy luận kiểu builder (hoặc suy luận builder), điều này có thể hữu ích khi bạn làm việc với các builder tổng quát. Nó giúp trình biên dịch suy luận các tham số kiểu của một cuộc gọi builder dựa trên thông tin kiểu về các cuộc gọi khác trong đối số lambda của nó.
Xem xét ví dụ sử dụng buildMap():

fun addEntryToMap(baseMap: Map<String, Number>, additionalEntry: Pair<String, Int>?) {
   val myMap = buildMap {
       putAll(baseMap)
       if (additionalEntry != null) {
           put(additionalEntry.first, additionalEntry.second)
       }
   }
}

Ở đây không có đủ thông tin kiểu để suy luận tham số kiểu theo cách thông thường, nhưng suy luận builder có thể phân tích các cuộc gọi trong đối số lambda. Dựa trên thông tin kiểu về các cuộc gọi putAll()put(), trình biên dịch có thể tự động suy luận các tham số kiểu của cuộc gọi buildMap() thành StringNumber. Suy luận builder cho phép bỏ qua tham số kiểu khi sử dụng builder tổng quát.

1. Viết builder của bạn

1.1 Yêu cầu để kích hoạt suy luận builder

Trước Kotlin 1.7.0, việc kích hoạt suy luận builder cho một hàm builder yêu cầu tùy chọn trình biên dịch -Xenable-builder-inference. Trong 1.7.0, tùy chọn này được kích hoạt mặc định.
Để suy luận builder hoạt động cho builder của bạn, đảm bảo khai báo của nó có một tham số lambda builder của kiểu hàm với receiver. Cũng có hai yêu cầu cho kiểu receiver:
1. Nó phải sử dụng các tham số kiểu mà suy luận builder được giả sử suy luận. Ví dụ:
fun buildList(builder: MutableList.() -> Unit) { … }

Lưu ý rằng việc truyền kiểu tham số trực tiếp như fun myBuilder(builder: T.() -> Unit) chưa được hỗ trợ.
2. Nó phải cung cấp các thành viên công cộng hoặc các phần mở rộng chứa các tham số kiểu tương ứng trong chữ ký của chúng. Ví dụ:

class ItemHolder<T> {
    private val items = mutableListOf<T>()

    fun addItem(x: T) {
        items.add(x)
    }

    fun getLastItem(): T? = items.lastOrNull()
}

fun <T> ItemHolder<T>.addAllItems(xs: List<T>) {
    xs.forEach { addItem(it) }
}

fun <T> itemHolderBuilder(builder: ItemHolder<T>.() -> Unit): ItemHolder<T> =
    ItemHolder<T>().apply(builder)

fun test(s: String) {
    val itemHolder1 = itemHolderBuilder { // Type of itemHolder1 is ItemHolder<String>
        addItem(s)
    }
    val itemHolder2 = itemHolderBuilder { // Type of itemHolder2 is ItemHolder<String>
        addAllItems(listOf(s))
    }
    val itemHolder3 = itemHolderBuilder { // Type of itemHolder3 is ItemHolder<String?>
        val lastItem: String? = getLastItem()
        // ...
    }
}

1.2. Các tính năng được hỗ trợ

Suy luận builder hỗ trợ:
* Suy luận nhiều tham số kiểu

fun <k, v=""> myBuilder(builder: MutableMap<k, v="">.() -> Unit): Map<k, v=""> { ... }

* Suy luận tham số kiểu của nhiều lambda builder trong một cuộc gọi bao gồm cả những lambda tương tác

fun <K, V> myBuilder(
    listBuilder: MutableList<V>.() -> Unit,
    mapBuilder: MutableMap<K, V>.() -> Unit
): Pair<List<V>, Map<K, V>> =
    mutableListOf<V>().apply(listBuilder) to mutableMapOf<K, V>().apply(mapBuilder)

fun main() {
    val result = myBuilder(
        { add(1) },
        { put("key", 2) }
    )
    // result has Pair<List<Int>, Map<String, Int>> type
}

* Suy luận tham số kiểu mà các tham số kiểu của nó là tham số hoặc kiểu trả về của lambda

fun <K, V> myBuilder1(
    mapBuilder: MutableMap<K, V>.() -> K
): Map<K, V> = mutableMapOf<K, V>().apply { mapBuilder() }

fun <K, V> myBuilder2(
    mapBuilder: MutableMap<K, V>.(K) -> Unit
): Map<K, V> = mutableMapOf<K, V>().apply { mapBuilder(2 as K) }

fun main() {
    // result1 has the Map<Long, String> type inferred
    val result1 = myBuilder1 {
        put(1L, "value")
        2
    }
    val result2 = myBuilder2 {
        put(1, "value 1")
        // You can use `it` as "postponed type variable" type
        // See the details in the section below
        put(it, "value 2")
    }
}

2. Cách suy luận builder hoạt động

2.1 Biến kiểu trì hoãn

Suy luận builder hoạt động dựa trên biến kiểu trì hoãn, xuất hiện trong lambda builder trong quá trình phân tích suy luận builder. Một biến kiểu trì hoãn là kiểu của tham số kiểu, đang trong quá trình suy luận. Trình biên dịch sử dụng nó để thu thập thông tin kiểu về tham số kiểu.
Xem xét ví dụ với buildList():

val result = buildList {
    val x = get(0)
}

Ở đây, x có kiểu là biến kiểu trì hoãn: cuộc gọi get() trả về một giá trị kiểu E, nhưng E chưa được cố định. Tại thời điểm này, kiểu cụ thể cho E là chưa biết.
Khi giá trị của biến kiểu trì hoãn được liên kết với một kiểu cụ thể, suy luận builder thu thập thông tin này để suy luận kiểu kết quả của tham số kiểu tương ứng vào cuối quá trình phân tích suy luận builder. Ví dụ:

val result = buildList {
    val x = get(0)
    val y: String = x
} // result has the List<String> type inferred

Sau khi biến kiểu trì hoãn được gán cho một biến kiểu String, suy luận builder nhận thông tin rằng x là một kiểu con của String. Phép gán này là câu lệnh cuối cùng trong lambda builder, vì vậy quá trình phân tích suy luận builder kết thúc với kết quả suy luận tham số kiểu E thành String.
Lưu ý rằng bạn luôn có thể gọi các hàm equals(), hashCode(), và toString() với biến kiểu trì hoãn như là một receiver.

2.2 Đóng góp vào kết quả suy luận builder

Suy luận builder có thể thu thập nhiều loại thông tin kiểu khác nhau góp phần vào kết quả phân tích. Nó xem xét:
* Gọi các phương thức trên receiver của lambda sử dụng kiểu của tham số kiểu

val result = buildList {
    // Type argument is inferred into String based on the passed "value" argument
    add("value")
} // result has the List<String> type inferred

* Xác định kiểu mong đợi cho các cuộc gọi trả về kiểu của tham số kiểu

val result = buildList {
    // Type argument is inferred into Float based on the expected type
    val x: Float = get(0)
} // result has the List<Float> type
class Foo<T> {
    val items = mutableListOf<T>()
}

fun <K> myBuilder(builder: Foo<K>.() -> Unit): Foo<K> = Foo<K>().apply(builder)

fun main() {
    val result = myBuilder {
        val x: List<CharSequence> = items
        // ...
    } // result has the Foo<CharSequence> type
}

* Truyền các kiểu của biến kiểu trì hoãn vào các phương thức mong đợi kiểu cụ thể

fun takeMyLong(x: Long) { ... }

fun String.isMoreThat3() = length > 3

fun takeListOfStrings(x: List<String>) { ... }

fun main() {
    val result1 = buildList {
        val x = get(0)
        takeMyLong(x)
    } // result1 has the List<Long> type

    val result2 = buildList {
        val x = get(0)
        val isLong = x.isMoreThat3()
    // ...
    } // result2 has the List<String> type

    val result3 = buildList {
        takeListOfStrings(this)
    } // result3 has the List<String> type
}

* Lấy tham chiếu có thể gọi đến thành viên của receiver của lambda

fun main() {
    val result = buildList {
        val x: KFunction1<Int, Float> = ::get
    } // result has the List<Float> type
}
fun takeFunction(x: KFunction1<Int, Float>) { ... }

fun main() {
    val result = buildList {
        takeFunction(::get)
    } // result has the List<Float> type
}

Cuối cùng của quá trình phân tích, suy luận builder xem xét tất cả thông tin kiểu đã thu thập và cố gắng hợp nhất nó thành kiểu kết quả. Xem ví dụ.

val result = buildList { // Inferring postponed type variable E
    // Considering E is Number or a subtype of Number
    val n: Number? = getOrNull(0)
    // Considering E is Int or a supertype of Int
    add(1)
    // E gets inferred into Int
} // result has the List<Int> type

Kiểu kết quả là kiểu cụ thể nhất tương ứng với thông tin kiểu đã thu thập trong quá trình phân tích. Nếu thông tin kiểu được cung cấp mâu thuẫn và không thể hợp nhất, trình biên dịch báo cáo lỗi.
Lưu ý rằng trình biên dịch Kotlin chỉ sử dụng suy luận builder nếu suy luận kiểu thông thường không thể suy luận được tham số kiểu. Điều này có nghĩa là bạn có thể đóng góp thông tin kiểu bên ngoài lambda builder, và sau đó không cần phải phân tích suy luận builder. Xem ví dụ:

fun someMap() = mutableMapOf<CharSequence, String>()

fun <E> MutableMap<E, String>.f(x: MutableMap<E, String>) { ... }

fun main() {
    val x: Map<in String, String> = buildMap {
        put("", "")
        f(someMap()) // Type mismatch (required String, found CharSequence)
    }
}

Ở đây, một không phù hợp kiểu xuất hiện vì kiểu mong đợi của bản đồ được chỉ định bên ngoài lambda builder.Map<in String, String>.

Cảm ơn mọi người đã đồng hành cùng chúng tôi trên Cafedev trong hành trình khám phá về Kotlin và sức mạnh của việc sử dụng builders với tính năng suy luận kiểu builder. Tại Cafedev, chúng tôi hy vọng rằng những kiến thức và thông tin chia sẻ đã giúp mọi người mở rộng kiến thức và kỹ năng lập trình của mình. Đừng ngần ngại thảo luận, đặt câu hỏi và chia sẻ ý kiến của bạn trên nền tảng Cafedev. Hãy tiếp tục đồng hành cùng chúng tôi để khám phá thêm về thế giới lập trình tại Cafedev!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của Cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!