如何编写以下 Kotlin 合约?
How to write the following Kotlin contract?
问题很简单:(using 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 版本可以做到这一点吗?如果是,怎么做?
这目前是不可能的。合同不能基于合同中class的属性,所以当您在合同中检查latitude
或longitude
时,这是不允许的。
你可以这样做:
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)
}
但是你不会有空值的编译时异常。这意味着它不会强制您设置这两个属性(纬度和经度)
您可以将“{}”替换为“()”并使用命名参数:
val loc = Location(
lat = 2.0
lng = 2.0
)
另请注意,这是 class 的构造函数,不需要构建器 :) 这适用于 Kotlin 中的所有调用。
见https://kotlinlang.org/docs/reference/functions.html#named-arguments
问题很简单:(using 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 版本可以做到这一点吗?如果是,怎么做?
这目前是不可能的。合同不能基于合同中class的属性,所以当您在合同中检查latitude
或longitude
时,这是不允许的。
你可以这样做:
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)
}
但是你不会有空值的编译时异常。这意味着它不会强制您设置这两个属性(纬度和经度)
您可以将“{}”替换为“()”并使用命名参数:
val loc = Location(
lat = 2.0
lng = 2.0
)
另请注意,这是 class 的构造函数,不需要构建器 :) 这适用于 Kotlin 中的所有调用。
见https://kotlinlang.org/docs/reference/functions.html#named-arguments