Spec-Zone.ru › Kotlin 1.8

Использование билдеров с выводом типа билдера

Kotlin поддерживает вывод типа билдера (или вывод билдера), что может быть полезно при работе с универсальными билдерами. Он помогает компилятору выводить типы аргументов вызова билдера на основе информации о типах других вызовов внутри его лямбда-аргумента.

Рассмотрим этот пример использования buildMap():

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

Здесь недостаточно информации о типе для вывода типов аргументов стандартным способом, но вывод типа билдера может проанализировать вызовы внутри лямбда-аргумента. На основе информации о типе putAll() и put() вызовах компилятор может автоматически вывести типы аргументов вызова buildMap() в String и Number. Вывод типа билдера позволяет опустить аргументы типов при использовании универсальных билдеров.

Создание собственных билдеров

Требования для включения вывода типа билдера

До Kotlin 1.7.0 для включения вывода типа билдера для функции билдера требовался параметр компилятора -Xenable-builder-inference. В 1.7.0 он включен по умолчанию.

Чтобы вывод типа билдера работал для вашего собственного билдера, убедитесь, что его объявление имеет лямбда-параметр билдера типа функции с получателем. Существуют также два требования к типу получателя:

  1. Он должен использовать типы аргументов, которые должен выводить вывод типа билдера. Например:

    fun <V> buildList(builder: MutableList<V>.() -> Unit) { ... }
    

    Обратите внимание, что передача типа параметра напрямую, как fun <T> myBuilder(builder: T.() -> Unit), пока не поддерживается.

  2. Он должен предоставлять публичные члены или расширения, которые содержат соответствующие параметры типа в своей сигнатуре. Например:

    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()
            // ...
        }
    }
    

Поддерживаемые возможности

Вывод типа билдера поддерживает:

  • Вывод нескольких аргументов типа

    fun <K, V> myBuilder(builder: MutableMap<K, V>.() -> Unit): Map<K, V> { ... }
    
  • Вывод аргументов типа нескольких лямбда-выражений билдера в одном вызове, включая взаимозависимые

    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
    }
    
  • Вывод аргументов типа, типы параметров которых являются параметром или возвращаемым типом лямбда-выражения

    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")
        }
    }
    

Как работает вывод типа билдера

Отложенные переменные типа

Вывод типа билдера работает с точки зрения отложенных переменных типа, которые появляются внутри лямбда-выражения билдера во время анализа вывода типа билдера. Отложенная переменная типа — это тип аргумента типа, который находится в процессе вывода. Компилятор использует его для сбора информации о типе аргумента.

Рассмотрим пример с buildList():

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

Здесь x имеет тип отложенной переменной типа: вызов get() возвращает значение типа E, но сам E еще не определен. В данный момент конкретный тип для E неизвестен.

Когда значение отложенной переменной типа связывается с конкретным типом, вывод типа билдера собирает эту информацию, чтобы вывести результирующий тип соответствующего аргумента типа в конце анализа вывода типа билдера. Например:

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

После того, как отложенная переменная типа присваивается переменной типа String, вывод типа билдера получает информацию о том, что x является подтипом String. Это присваивание — последнее утверждение в лямбда-выражении билдера, поэтому анализ вывода типа билдера завершается результатом вывода аргумента типа E в String.

Обратите внимание, что вы всегда можете вызвать функции equals(), hashCode(), и toString() с отложенной переменной типа в качестве получателя.

Вклад в результаты вывода типа билдера

Вывод типа билдера может собирать различные виды информации о типе, которые вносят вклад в результат анализа. Он рассматривает:

  • Вызов методов получателя лямбда-выражения, которые используют тип параметра

    val result = buildList {
        // Type argument is inferred into String based on the passed "value" argument
        add("value")
    } // result has the List<String> type inferred
    
  • Указание ожидаемого типа для вызовов, которые возвращают тип параметра

    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
    }
    
  • Передача типов отложенных переменных типа в методы, которые ожидают конкретные типы

    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
    }
    
  • Получение ссылка на вызываемый элемент получателя лямбда-выражения

    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
    }
    

В конце анализа вывод типа билдера рассматривает всю собранную информацию о типе и пытается объединить ее в результирующий тип. См. пример.

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

Результирующий тип — это наиболее специфичный тип, соответствующий информации о типе, собранной во время анализа. Если заданная информация о типе противоречива и не может быть объединена, компилятор сообщает об ошибке.

Обратите внимание, что компилятор Kotlin использует вывод типа билдера только в том случае, если стандартный вывод типа не может вывести аргумент типа. Это означает, что вы можете вносить информацию о типе вне лямбда-выражения билдера, а затем анализ вывода типа билдера не требуется. Рассмотрим пример:

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)
    }
}

Здесь появляется несоответствие типов, потому что ожидаемый тип карты указан вне лямбда-выражения билдера. Компилятор анализирует все операторы со фиксированным типом получателя Map<in String, String>.

Последнее изменение: 10 января 2023 г.
Безопасные по типу билдеры Безопасность от null

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/using-builders-with-builder-inference.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API