Overloading
Languages without support for default arguments often use overloading to imitate that feature.
The term overload refers to the name of a function: You use the same name
(“overload” that name) for different functions as long as the parameter lists
differ. Here, we overload the member function f():
// Overloading/Overloading.kt
package overloading
import atomictest.eq
class Overloading {
fun f() = 0
fun f(n: Int) = n + 2
}
fun main() {
val o = Overloading()
o.f() eq 0
o.f(11) eq 13
}
In Overloading, you see two functions with the same name, f(). The
function’s signature consists of the name, parameter list and return type.
Kotlin distinguishes one function from another by comparing signatures. When
overloading functions, the parameter lists must be unique—you cannot overload
on return types.
The calls show that they are indeed different functions. A function signature also includes information about the enclosing class (or the receiver type, if it’s an extension function).
If a class already has a member function with the same signature as an extension function, Kotlin prefers the member function. However, you can overload the member function with an extension function:
// Overloading/MemberVsExtension.kt
package overloading
import atomictest.eq
class My {
fun foo() = 0
}
fun My.foo() = 1 // [1]
fun My.foo(i: Int) = i + 2 // [2]
fun main() {
My().foo() eq 0
My().foo(1) eq 3
}
- [1] It’s senseless to declare an extension that duplicates a member, because it can never be called.
- [2] You can overload a member function using an extension function by providing a different parameter list.
Don’t use overloading to imitate default arguments. That is, don’t do this:
// Overloading/WithoutDefaultArguments.kt
package withoutdefaultarguments
import atomictest.eq
fun f(n: Int) = n + 373
fun f() = f(0)
fun main() {
f() eq 373
}
The function without parameters just calls the first function. The two functions can be replaced with a single function by using a default argument:
// Overloading/WithDefaultArguments.kt
package withdefaultarguments
import atomictest.eq
fun f(n: Int = 0) = n + 373
fun main() {
f() eq 373
}
In both examples you can call the function either without an argument or by
passing an integer value. Prefer the form in WithDefaultArguments.kt.
When using overloaded functions together with default arguments, calling the
overloaded function searches for the “closest” match. In the following example,
the foo() call in main() does not call the first version of the function
using its default argument of 99, but instead calls the second version, the one
without parameters:
// Overloading/OverloadedVsDefaultArg.kt
package overloadingvsdefaultargs
import atomictest.*
fun foo(n: Int = 99) = trace("foo-1-$n")
fun foo() {
trace("foo-2")
foo(14)
}
fun main() {
foo()
trace eq """
foo-2
foo-1-14
"""
}
You can never utilize the default argument of 99, because foo() always
calls the second version of f().
Why is overloading useful? It allows you to express “variations on a theme” more clearly than if you were forced to use different function names. Suppose you want addition functions:
// Overloading/OverloadingAdd.kt
package overloading
import atomictest.eq
fun addInt(i: Int, j: Int) = i + j
fun addDouble(i: Double, j: Double) = i + j
fun add(i: Int, j: Int) = i + j
fun add(i: Double, j: Double) = i + j
fun main() {
addInt(5, 6) eq add(5, 6)
addDouble(56.23, 44.77) eq
add(56.23, 44.77)
}
addInt() takes two Ints and returns an Int, while addDouble() takes
two Doubles and returns a Double. Without overloading, you can’t just name
the operation add(), so programmers typically conflate what with how to
produce unique names (you can also create unique names using random characters
but the typical pattern is to use meaningful information like parameter types).
In contrast, the overloaded add() is much clearer.
- -
The lack of overloading in a language is not a terrible hardship, but the
feature provides valuable simplification, producing more readable code. With
overloading, you just say what, which raises the level of abstraction and
puts less mental load on the reader. If you want to know how, look at the
parameters. Notice also that overloading reduces redundancy: If we must say
addInt() and addDouble(), then we essentially repeat the parameter
information in the function name.
Exercises and solutions can be found at www.AtomicKotlin.com.