The trivial option: just don't have overloads
Assuming function overloads are not dynamically dispatched based on the arguments' types, there is little need for them beyond the fact that it's nice for different versions of the same method to have the same name. But every program with function overloads can trivially be rewritten to not use overloads, by just giving the functions different names. So overloads are fundamentally just a convenience feature, and you can leave them out of a language without needing to offer an alternative.
In most cases it really is that simple, but there are two notable counterexamples:
- Overloaded constructors or initialisers in most languages can't be given different names, because they either don't have names (e.g. Java, Javascript), or their name is mandated (e.g. Python). This can be addressed by using static factory methods instead of constructor overloads, albeit with the small inconvenience that subclass constructors cannot invoke static factory methods instead of a superclass constructor.
- When two or more interfaces use the same name for methods with different signatures, if the interfaces belong to third-party libraries then the user cannot just rename them. In this case, overloading is necessary to implement both interfaces. However, this still leaves the problem where two interfaces have the same method name with the same signature, but you want to implement each interface differently.
Neither of these concerns really necessitate overloads, and overloads don't fully solve the second issue anyway, so you might well just decide to go the simple route and not have overloads at all.
Default arguments
Java's method overloads are mainly useful for things like this:
void foo() {
foo(0);
}
void foo(int bar) {
// ...
}
In many other languages such as Python, this is achieved more simply by giving bar
a default value:
def foo(bar=0):
# ...
It is difficult to support both overloads and default arguments, because some calls might be compatible with multiple overload signatures, where neither is "more specific". Normally languages only have one or the other; Typescript is a notable exception, but Typescript's overloads are ordered by priority and only affect the function's signature, not its implementation. So in Typescript, even if multiple overload signatures are applicable at a call-site, the highest-priority overload is selected, but also the runtime behaviour would be the same regardless.
The upsides of default arguments are that they can make code more readable, and don't require the function signatures to be duplicated; particular if there are multiple arguments with default values, there might need to be exponentially many overloads. That is, if three arguments all have default values then in a language like Java which has overloads instead of defaults, the user would need to write 23 = 8 different overloads to handle all the combinations.
One downside of default arguments is that they don't allow different "versions" of the function to have drastically different implementations. But in practice, overloads are mainly used to mimic default arguments, and even when they aren't, branching based on the argument values is generally sufficient.