Как написать следующий контракт 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)