如何编写以下 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
)

为了避免使用 !! 运算符,我想编写一个合同来帮助编译器推断出一个智能广播,该智能广播表明属性 latlng 不为空,但我有未能成功做到这一点。

我试过很多东西都没有成功,我想这可能是因为我没有完全理解合同的动态。这些是风格:



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的属性,所以当您在合同中检查latitudelongitude时,这是不允许的。

你可以这样做:

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