Compile time functions, macros


#1

To have a better performance, some times it is better to do some computations on compile time and generate code aka quote.
I am only propose to have compile time functions and not to be able to change the syntax.
All parameters are passed similar to Scala call by name.
Also in quote it will not be possibile to use return, so functions only generate code and not change the flow execution.


#2

Could you please provide some specific use cases where you need to perform such computations, and explain why you prefer to perform them on every compilation, rather than calculate the value once and include its result in the source code?


#3

Logging, no need to check logging level, library can do it for you:

will be rewritten into

Querydsl or JOOQ, genereate query string from dsl:

will be rewritten into

Routing:

val routes = arrayOf( get("/hello", (req, res) -> "Hello World"); get("/fizz", (req, res) -> "bazz"); ); dispatch(routes);
will be rewritten into
if (req.path == "/hello") { "Hello World"; } else if (req.path == "/fizz") { "bazz" }

So, it offers possibility to write less and concise code, but the result will be like we will write code without some library.
It will be very useful feature for library writers. You can see quote from Elixir.


#4

These are not compile-time functions, these are macros. While it is possible that macros will be added to Kotlin at some point, we don’t have any plans to work on them in the short or medium term.


#5

Yeap, macros. But with restrictions: not allow syntax change and not allow return in quote.


#6

Also it will be useful to create class based on interface, at the moment we can only do that in run-time using Proxy.
And it will run more faster, because it will be real class and not a proxy.


#7

Also with macros we can reverse routing


#8

You don’t need to explain all the usecases for macros; we’re aware of them. What I wrote above is still true.


#9

There’s a similar feature request regarding compile-time evaluated constant expressions: https://youtrack.jetbrains.com/issue/KT-14652


#10

Why don’t implement logging as a high order function?

log.debug {
//Some expensive computations
"Some message"
}

where block evaluated only when debug level allowed.

Other examples also strange. The SQL construction you can put into lazy field. The dispatching may just use cycle, why your suggestion better?


#11

For example, I have a data class

data class DayChartPoint(
    val date: Date,
    val open: Double,
    val high: Double,
    val low: Double,
    val close: Double,
    val volume: Long,
    val unadjustedVolume: Long,
    val change: Double,
    val changePercent: Double,
    val vwap: Double,
    val label: String,
    val changeOverTime: Double
)

and I have some other entity that should consume this data. For example, JavaFx, which expects the fields to be observable values. So one must have another class defined:

class DayChartPointBean {
    val changeOverTimeProperty = SimpleDoubleProperty()
    var changeOverTime by changeOverTimeProperty
    val labelProperty = SimpleStringProperty()
    var label by labelProperty
    val vwapProperty = SimpleDoubleProperty()
    var vwap by vwapProperty
    val changePercentProperty = SimpleDoubleProperty()
    var changePercent by changePercentProperty
    val changeProperty = SimpleDoubleProperty()
    var change by changeProperty
    val unadjustedVolumeProperty = SimpleLongProperty()
    var unadjustedVolume by unadjustedVolumeProperty
    val volumeProperty = SimpleLongProperty()
    var volume by volumeProperty
    val closeProperty = SimpleDoubleProperty()
    var close by closeProperty
    val lowProperty = SimpleDoubleProperty()
    var low by lowProperty
    val highProperty = SimpleDoubleProperty()
    var high by highProperty
    val openProperty = SimpleDoubleProperty()
    var open by openProperty
    val dateProperty = SimpleObjectProperty<Date>()
    var date by dateProperty
}

and then have a routine defined as well, that would perform a conversion from one data type to another:

fun DayChartPoint.toBean() =
    DayChartPointBean().let {
        it.change = this.change
        it.changeOverTime = this.changeOverTime
        it.changePercent = this.changePercent
        it.close = this.close
        it.date = this.date
        it.high = this.high
        it.label = this.label
        it.low = this.low
        it.open = this.open
        it.unadjustedVolume = this.unadjustedVolume
        it.volume = this.volume
        it.vwap = this.vwap
        it
    }

Going through the chore of creating this even once is not really what I’m looking for. As well as changing this all by hand in all places, if something in the data definition changes.

And if I want to use JavaFx, there’s nothing that I can do, but to provide the data in the format that framework expects. Other, non-JavaFx, examples abound.

If the DayChartPointBean class and DayChartPoint.toBean() function definition could be generated at compile time, this would certainly save me a lot of time and give me a lot of confidence that once changes to the DayChartPoint were made, corresponding classes and methods would be updated and updated correctly.


#12

You can use annotation processors to generate those classes; all necessary support for it is available in Kotlin today.


#13

Sort of. Annotation processors are quite complicated to set up and are target (JVM) specific.

EDIT: You also have to use Java reflection API inside annotation processors to get info about Kotlin classes, AFAIK. Which is awkward:

data class Person(
    val name: String,
    var age: Int
)

Person::class.java.declaredMethods.forEach {
    println("${it.name}: ${it.returnType}")
}

prints

equals: boolean
toString: class java.lang.String
hashCode: int
getName: class java.lang.String
copy: class com.vitalyk.kotlin.sandbox.LanguageKt$javaReflection$Person
setAge: void
getAge: int
component1: class java.lang.String
component2: int
copy$default: class com.vitalyk.kotlin.sandbox.LanguageKt$javaReflection$Person

Using Kotlin’s reflection API

Person::class.memberProperties.forEach {
    println("${ if (it is KMutableProperty<*>) "var" else "val" } ${it.name}: ${it.returnType}")
}

we get

var age: kotlin.Int
val name: kotlin.String

So it’s much easier to generate new Kotlin classes, since you don’t have to guess which Java methods were generated from which Kotlin properties of the original Kotlin class.

There’s also a relative dearth of information and documentation about kapt.


#14

Yeah, annotation processing has been pretty much a nightmare for me these past couple days. I can’t really call it a clean solution. Currently stuck at roundEnv.rootElements not listing data classes, only regular Kotlin classes. Kotlin’s own reflection is so much cleaner and easier to use. There’s got to be a better way.