Why is `tailrec` a modifier keyword applied at the function level?


There are actually 2 parts to this question:

  1. tailrec, IMHO, stands out as an oddball in the modifier keyword group as it has no effect on semantics, instead operating in compiler-space optimization world. Why isn’t it e.g. an annotation?

  2. It seems to me that applying it at the function declaration level is restrictive. Consider the following:

tailrec fun f(x: Int): Int = when {
    x <= 10 -> x
    x % 2 == 1 -> f(x - 7) + 1
    else -> f(x + 1)

In this case I have no way to specify that I intend one branch to be tail recursive and the other not to be. A related issue is that I may actually intend for one such branch to be tail recursive but the function as a whole is tail recursive so long as some other branch makes a tail recursive call.

An approach that seems better all around (to me) would be an annotation at the expression level:

fun f(x: Int): Int = when {
    x <= 10 -> x
    x % 2 == 1 -> f(x - 7) + 1
    else -> @TailRec f(x + 1)

it just seems like the “right” place to put it. The compiler’s job is very straightforward as well, probably even simpler than its current responsibilities.

As a bonus [and from https://stackoverflow.com/questions/35714683/kotlin-tail-recursion-for-mutually-recursive-functions I gather that the JVM doesn’t support mutual recursion (or “proper tail calls” in the general case) but if it were to be supported in the future] this approach could be easily extended:

fun f(x: Int): Int = if (x > 100) @TailCall g(x / 2) else x
fun g(x: Int): Int = @TailCall f(x + 4)