Как написать следующий контракт Kotlin?

Вопрос очень простой: (используя Kotlin 1.3.71)

У меня есть следующие данные, похожие на эти:

data class Location(val lat: Double, val lng: Double)

Я хочу добиться безопасности типов с помощью такого вызова:

val loc = location {
    lat = 2.0
    lng = 2.0
}

Для этого я построил:


fun location(builder: LocationBuilder.() -> Unit): Location {
    val lb = LocationBuilder().apply(builder)
    return Location(lb.lat!!, lb.lng!!)
}

data class LocationBuilder(
    var lat: Double? = null,
    var lng: Double? = null
)

Чтобы избежать !! операторов, я хотел бы написать контракт, который поможет компилятору вывести смарткаст, в котором говорится, что атрибуты lat а также lng не равны нулю, но мне не удалось это сделать успешно.

Я пробовал что-то безуспешно, и я считаю, что это могло быть потому, что я не полностью понимаю динамику контрактов. Это стиль:



fun LocationBuilder.buildSafely(dsl: LocationBuilder.()->Unit): LocationBuilder {
    contract {
        returnsNonNull() implies (this@buildSafely.lat != null && this@buildSafely.lng != null)
    }
    apply(dsl)
    if(lat == null || lng == null) throw IllegalArgumentException("Invalid args")

    return this
}

fun location(builder: LocationBuilder.()->Unit): Location {
    val configuredBuilder = LocationBuilder().buildSafely(builder)

    return Location(configuredBuilder.lat, configuredBuilder.lng)
    /* I would expect a smart cast but I am getting a compile error stating that lat and lng may still be null */
}

Итак, вопрос:

Можно ли это сделать в текущей версии Kotlin? Если да, то как?

3 ответа

В настоящее время это невозможно. Контракт не может быть основан на свойствах класса в контракте, поэтому при проверкеlatitude или longitude в контракте это не допускается.

Вы можете просто заменить '{}' на '()' и использовать именованные аргументы:

val loc = Location(
  lat = 2.0
  lng = 2.0
)

Также обратите внимание, что это конструктор класса, конструктор не нужен:) Это работает для всех вызовов в Kotlin.

См. https://kotlinlang.org/docs/reference/functions.html named- arguments

Вы можете сделать что-то вроде этого:

import kotlin.properties.Delegates

fun location(builder: LocationBuilder.() -> Unit): Location {
    val lb = LocationBuilder().apply(builder)
    return Location(lb.lat, lb.lng)
}


class LocationBuilder {
    var lat by Delegates.notNull<Double>()
    var lng by Delegates.notNull<Double>()
}

data class Location(
    val lat: Double,
    val lng: Double
)

fun main() {
    val l = location {
        lat = 2.0
        lng = 8.0
    }

    println(l)
}

Но у вас не будет исключения времени компиляции для нулей. Это означает, что это не заставляет вас устанавливать оба свойства (lat и lng)

Другие вопросы по тегам