Ad-hoc polymorphism (method “overloading”) is a quite unremarkable feature of a language and does not make it more expressive. I.e. there is literally nothing that overloading lets you do that you can't easily do without (ignoring generic programming). All it does is providing a shortcut syntax.
When a language with ad-hoc polymorphism (such as Java) compiles a call to Overload#demo(…)
, it sees the different overloads void demo(int)
and void demo(int, int)
as entirely different functions. To resolve the call, the compiler matches the call signature to the available overloads, and selects the best match. This can be re-implemented in a dynamic language by inspecting the number and types of arguments. When ad-hoc polymorphism is resolved based on argument types during runtime, this is actually more expressive than ad-hoc polymorphism, and might be called multi-methods instead.
But we don't need any fancy metaprogramming to get the equivalent of ad-hoc polymorphism. Instead, we can simply give a different name to each overload, and require the programmer to select the proper overload when writing the code. This is exactly equivalent to the stuff the Java compiler would be doing, although clearly more error prone. You actually see this sometime in C APIs where there might be a function something(int)
and something2(int, int)
where we have different numbers of arguments, or int atoi(const char*)
and double atof(const char*)
where we have different types. In your example, your interface might be implemented as Provider.payWithPayDetails(PayDetails)
instead of Provider.pay(PayDetails)
.
So if ad-hoc polymorphism is pretty much useless, why do languages have this feature? There are two main reason:
In Java, ad-hoc polymorphism allows us to simulate optional arguments, and to provide multiple constructors with different signatures. E.g. we could initialize a point from three numbers Point(double x, double y, double z)
, or as a copy constructor from another point Point(Point orig)
. In my experience, it would have been better for the language to provide actual optional arguments, and to suggest the use of suitably named static factory functions instead of overloading constructors. Note that PHP has real optional arguments, and no need to fake them via ad-hoc polymorphism.
Operator overloading would usually be implemented using ad-hoc polymorphism, since you can't give a different name to the same operator just to use it for different types. You wouldn't want to have +int
and +double
(though OCaml actually does that with +
and +.
operators, I think). In a wider sense, any function can be seen as an operator (or at least an operation) on some value. This comes in quite useful when writing generic code, e.g. with C++ templates (“argument-dependent lookup”) or Haskell's generics. Especially with Haskell, we might have dozens of definitions for a global function like fmap
, but this can be resolved during compilation to a single instance. However, this use case does not apply to Java where interfaces are the only mechanism to define abstract types.