`tailrec` is probably one of least used Kotlin fea...
# language-evolution
s
tailrec
is probably one of least used Kotlin features, and this is me wondering if it couldn't be improved with some additional syntactic sugar (not that it'd improve its usage much). TLDR: can we make the accumulator parameter invisible from the outside!? So let's start with a
factorial
-function using tail recursion:
Copy code
tailrec fun factorial(n: Int, f:Long): Long {
    if(n<2) return f
    return factorial(n-1, n*f)
}
my problem is the second parameter, the accumulator. For
factorial
is only valid parameter for it is
1
. But I don't want to hardcode
1
at every invocation and/or even give a programmer a chance to provide the wrong value. So let's use an optional parameter:
Copy code
tailrec fun factorial(n: Int, f:Long = 1): Long {
    if(n<2) return f
    return factorial(n-1, n*f)
}
at least now I don't have to provide the initial value anymore, i can write
val x = factorial(6)
, but unfortuanately I can still provide a wrong parameter. An IDE will still suggest, that I provide one. So if I want to avoid that, I have to wrap my tailrec-fun inside a normal function.
Copy code
fun factorial(n: Int): Long {
    tailrec fun _factorial(n: Int, f:Long): Long {
        if(n<2) return f
        return _factorial(n-1, n*f)
    }
    return _factorial(n, 1)
}
now the using
factorial
is completely safe, but there is boilerplatecode involved and invocation overhead. So how about Kotlin gains a new keyword (let's call it)
acc
(for accumulator) where you have to provide an initialisation value and you can't set the parameter from outside the function itself!
Copy code
tailrec fun factorial(n: Int, acc f:Long = 1): Long {
    if(n<2) return f
    return factorial(n-1, n*f)
}
now we're back to boilercode-free code that can only be invoked savely from the outside with one parameter. Alternative to naming it
acc
: a new keyword maybe to much to ask for, so how about using
private
. A private parameter (of a non-private function) has to have a default value which can only be supplied from within a private invocation context, so recursion would work 🤷‍♂️
Copy code
tailrec fun factorial(n: Int, private f:Long = 1): Long {
    if(n<2) return f
    return factorial(n-1, n*f)
}
question 1: if
private
parameters are allowed, what about
internal
parameters? question 2: Independently which name is used for the keyword, a remaining question is if this keyword should only be usabe within tailrec functions 🤔 (My initial thoughts are that I'd prefer
acc
over
private
if it's only allowed in tailrec fuctions)
s
For me, using a local function seems like a good enough solution. I'd be concerned that adding a new keyword or rule only for recursive functions would make them less accessible, not more 🤔
y
DeepRecursiveFunction
does something very similar to this. Instead of the accumulating parameter being an Integer, it's instead the
Continuation
which contains all the multiplications etc that need to be done. It'll likely be less efficient though, but perhaps some optimizations can be done for continuations (namely defunctionalization, and then using properties of multiplication and stuff) instead of introducing magic accumulating parameters into the language
s
So what got me thinking about tailrec was I've met people at the Kotlin meetup in Berlin (actually when the meeting was in Jetbrains office) who didn't yet figure out that you can clean up the accumulation parameter of a tailrec-function by moving the tailrec-code into a local function. And when I check the Kotlin docs about tailrec the example comes without an accumulation parameter. So how about adding a second example showcasing the use of a local function when an accumulation parameter is required. Feel free to use my factorial-example 😅 And since it's a single-expression function I can clean-up the code a bit:
Copy code
fun factorial(n: Int): Long {
    tailrec fun _factorial(n: Int, acc: Long): Long =
        if(n<2) acc else _factorial(n-1, n*acc)
    
    return _factorial(n, 1)
}
ok, I added a Youtrack-issue for this 🤓 Let's see if it sticks.
👍 1