Stephan Schröder
04/04/2025, 1:27 PMtailrec
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:
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:
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.
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!
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 🤷♂️
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)Sam
04/04/2025, 1:33 PMYoussef Shoaib [MOD]
04/04/2025, 1:48 PMDeepRecursiveFunction
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 languageStephan Schröder
04/04/2025, 2:42 PMfun 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)
}
Stephan Schröder
04/08/2025, 9:16 AM