Hi all! Has anyone yet considered adding compile t...
# language-proposals
p
Hi all! Has anyone yet considered adding compile time variables to Kotlin? This could be done either through an
@comptime
annotation, or even as a Comptime<A> trait. They would act as normal variables but would be evaluated at compile time instead of at runtime. This would allow for very powerful patterns - doing things like compile time bounds or units of measure checking. It could even allow us to make the nullibility checking and elvis operators part of the standard library written in Kotlin, instead of being part of the compiler itself
m
There is
constexpr
proposal on the issue tracker https://youtrack.jetbrains.com/issue/KT-14652
From the issue:
This is one of such features that require a few really strong use cases before we consider them. Feel free to list the use cases in the comments below
p
Thanks for the pointer Matej! My proposal is similar, but different in some important ways. It’s inspired more by “comptime” from Zig / Lightweight Modular Staging from Scala, than c++ constexpr. C++ constexpr cannot be used as function / class parameters, which severely limits their usefulness. For example, in my proposal, we could have a class
inline class BoundedArray(val arr: Array<String>, comptime val length: Int) {
fun zip(other: BoundedArray): BoundedArray {
assert(length == other.length)
...
}
}
where the length parameter is only present at compile time , and it verifies that for all callers of zip the two arrays are of the same length.
m
Sorry, I don't think I get your example
I guess you expect that all arrays passed into BoundedArray are compile time?
d
That looks like templating in C++.
p
@Matej Drobnič In this case yes, which i agree is a bit contrived. Let me give you a better example: units of measure. Let say you wanted to enforce the units of measure algebra - you can multiply /divide anything, but you can only add when the units of measure are the same. The way you could do that is
Copy code
enum Dimension { METER, SECOND, KG }
data class UOM(val dims: MutableMap<Dimension, Int>) {
    operator fun times(other: UOM): UOM {
        val newMap = HashMap<Dimension, Int>()
        for (dim in dims.entries) {
            val otherDim = other.dims.get(dim.key)
            if (otherDim == null) newMap.put(dim.key, dim.value)
            else newMap.put(dim.key, dim.value + otherDim)
        }
        for (dim in other.dims.entries) {
            if (!other.dims.contains(dim.key)) {
                newMap.put(dim.key, dim.value)
            }
        }
        return UOM(newMap)
    }
}

inline class Unit(val value: Double, comptime uom: UOM) {
  operator fun plus(other: Unit): Unit {
    assert(uom == other.uom)
    return Unit(value + other.value, uom)
  }
  operator fun times(other: Unit): Unit {
    return Unit(value * other.value, uom * other.uom)
  }
}
notice that the Unit class is an inline class, so the comptime uom would only be present at compile time. And yes, that does mean that you actually have to know your units at compile time, but often that’s actually true. It is SOMEWHAT similar to C++ templates, but i feel like trying to achieve this same thing with template expansion would be more difficult and unergonomic
d
There are examples of a use case here. I'm assuming the benefit you're seeing is stronger compile time guarantees (and not performance). I don't think the approach for reaching it is very well thought out, and I think something like it it would be implemented through extension of the kotlin contracts feature, if it were chosen to be implemented.
p
So in my mind you get both stronger compile-time guarantees, and better runtime performance (by avoiding unnecessary condition checking at runtime). You could also actually use this for code generation purposes in inline code by checking a condition at compile time and then generating the appropriate branch. So in my thinking it’s as a pretty general technique.
I’m curious why you think this approach is not well thought out? I agree that contracts can achieve some of the benefits, but they seem both more restrictive and somewhat less intuitive (though I admit that intuitiveness is extremely subjective :)) Are you worried about how one would actually implement this, or do you think the feature itself is poorly thought out?
d
Well, I don't know how certain classes would be selected and made to run at compile time. Would it use a jvm process? How does it map to other platforms? Et
Apologies though, these are things that can be considered at a later stage of course :)
I can see it being quite powerful in inline functions, where you may be able to avoid multiple inlining using const conditions
p
Actually, these are very reasonable questions, especially since the compilation model becomes a bigger part of the actual language. The idea here is that the Kotlin compiler would have to have the ability to run user code, either by being able to run its compilation artifacts, or having a separate Kotlin interpreter. Then, if you have
Copy code
fun a(x: Int) = x + 3

@inline fun b(x: Int, @comptime y: Int) {
  val c = a(y)
  return if (c > 4) 1 else 0
}
then the function “a” actually runs as part of the compilation of “b” , so either we have to be able to compile “a” before “b” or to interpret it. If i were building a brand new language, I would make it explicitly staged - the compiler compiles a “type level program”, which when run generates the “value level program”, which then gets compiled to the final bytecode. so the compilation pipeline would look like
Copy code
[Source Program] -- compile --> [CodeGenerator] --run--> [Generated Source] --compile--> ByteCode
I’m honestly not entirely sure how we would do this in the context of the existing compiler, but probably by having some restrictions on the code that can be marked as @comptime (only pure functions, only immutable data, no state) and then having an interpreter, but compiling would also work as long as we can actually run the output of our compilation.
d
Definitely an interesting idea. I'm not sure how demonstrable the performance benefits could be and the work required for implementation would obviously be extensive, so it may be difficult to justify or prioritize over other ideas and KEEPs. But you could consider looking at the KEEPs and writing a proposal there.