2

我正在尝试使用 Kotlin 的内联类来表达具有测量单位的类型安全操作。例如,让我们定义距离、时间和速度的单位:

inline class Meters(val v: Float) {
    operator fun plus(other: Meters) = Meters(v + other.v)
    operator fun times(amount: Float) = Meters(v * amount)
    operator fun compareTo(other: Meters) = v.compareTo(other.v)
    operator fun div(other: Meters): Float = v / other.v

    fun calcSpeed(time: Seconds) = MetersPerSecond(v * time.v)
    // operator fun times(time: Seconds) = MetersPerSecond(v / time.v) // clash (for now?)
}

inline class Seconds(val v: Float) {
    operator fun plus(other: Seconds) = Seconds(v + other.v)
    operator fun times(amount: Float) = Seconds(v * amount)
    operator fun compareTo(other: Seconds) = v.compareTo(other.v)
    operator fun div(other: Seconds): Float = v / other.v

    fun calcSpeed(distance: Meters) = MetersPerSecond(distance.v / v)
}

inline class MetersPerSecond(val v: Float) {
    operator fun plus(other: MetersPerSecond) = MetersPerSecond(v + other.v)
    operator fun times(amount: Float) = MetersPerSecond(v * amount)
    operator fun compareTo(other: MetersPerSecond) = v.compareTo(other.v)

    fun calcDistance(time: Seconds) = Meters(v * time.v)
    fun calcTime(distance: Meters) = Seconds(distance.v / v)
}

这里的想法是为以下操作定义方法:

  1. 保持单位不变(例如:求和、纯值的乘法)
  2. 结果为纯值(例如:除以同一单位的值)
  3. 产生另一个定义单位的值(例如time * distance = speed:)

这样就可以编写如下表达式:

val distance = Meters(1f)
val time = Seconds(1f)
val speed: MetersPerSecond = (distance * 0.5f).calcSpeed(time)

查看示例中属于情况 1 和 2 的运算符,我可以清楚地看到一个模式,我想知道是否有一种方法可以为所有需要此“单元”的类型以更通用的方式定义这些方法一次测量”的行为。

我考虑过拥有一个通用接口并将运算符定义为具有泛型的扩展函数:

interface UnitOfMeasurement { val v: Float }
operator fun <T: UnitOfMeasurement> T.plus(other: T) = T(v + other.v)

但这当然行不通,因为我无法实例化这样的泛型。有什么办法可以做到这一点?

4

1 回答 1

1

您可以使用在类型 T 上具体化的测量单位的工厂功能。然后在您的运算符中使用它:

interface UnitOfMeasurement { val v: Float }

inline class Meter(override val v: Float) : UnitOfMeasurement

inline class Second(override val v: Float) : UnitOfMeasurement

inline fun <reified T : UnitOfMeasurement> create(v: Float): T {
    return when (T::class) {
        Meter::class -> Meter(v) as T
        Second::class -> Second(v) as T
        else -> throw IllegalArgumentException("don't know how to create ${T::class}")
    }
}

inline operator fun <reified T : UnitOfMeasurement> T.plus(other: T) = create<T>(v + other.v)

fun main() {
    val a = Meter(10f)
    val b = Meter(5f)
    println(a + b)
    
    val c = Second(60f)
    val d = Second(30f)
    println(c + d)
    
    // println(a + c) // throws IllegalArgumentException
}

可以在这里试试

于 2021-09-21T14:20:42.953 回答