I guess this article is a fun discussion and a nice comparison of language features, maybe I'm taking it too seriously.
I guess this article is a fun discussion and a nice comparison of language features, maybe I'm taking it too seriously.
Hm, really? I didn't get the memo :-) After all, there is no semantic difference between function overloading and varying behavior based on args and kwargs.
You can trivially compile any set of overloaded functions into a single function with a bunch of if/else statements at the top level, without any change to the caller side. Basically, you need either variable arguments or function overloading to support a dynamic API. Nearly all languages support either one, many support both. Why is that a bad idea?
Languages like Elixir encourage heavy function overloading (to the point that some people replace nearly every potential if/case by a bunch of overloaded private functions), and I don't think its ecosystem is hurting for it.
Sidenote: I also really like how TypeScript does function overloading: you can only define one function implementation, but you can give it multiple overloaded typed signatures. This lets you say stuff like "if the first parameter is a string, then the second one must be a number". But you still have to implement that top-level if/else by hand, like you would in JavaScript. It's nice and pragmatic!
In the other hand, overloads can do entirely arbitrary actions. If there is no consistency between the different overloads, then there is no reason to have them be named the same thing.
If you can get away with just a bunch of named kwargs after the arguments that is fine, but I'd take overloading over the `args, kwargs` garbage any day, even if that is the more "pythonic" way.
def func(*args, **kwargs)
I am in full support of actual kwargs with names, it's the wildcard ones that I don't like. def func(args, flag=False, flag2=True) def func(*args, **kwargs)
is valid and completely ambiguous - you have no idea what it's doing unless you read the source, and track down how it branches out. IDEs are stopped dead in their tracks as they aren't going to parse the logic to figure it outAnd yes it's not the most common approach, but I have seen it enough times to despise it. This overloading approach removes the need for it so I'm all for that.
Ish, see if your IDE supports it (hint: it won't, but it will support named kwargs).
from functools import singledispatch
from typing import Union
@singledispatch
def myfunc(arg:Union[int, str]):
print("Called with something else")
@myfunc.register
def _(arg:int):
print("Called the one with int")
@myfunc.register
def _(arg:str):
print("Called the one with str")
myfunc(123)
myfunc("123")
Everything works as you'd expect, and the IDE's type hints are accurate as long as you actually put the correct type hint def myfunc(arg:Union[int, str]):It adds cognitive load when done badly, but reduces it when done well and consistently. The knee jerk dismissal of different approaches is probably the main reason why programming as an art has been by and large stagnant for decades.
Historically in C++ it was considered to be more performant to dispatch function calls on the base type of its arguments, not the concrete types.
Consider for simplicity member functions A::f(x...) in C++ as free functions where the first argument is fixed to be of type A, as in f(A, x...). If you want to emulate multiple dispatch for just 2 arguments f(A, B), you already find yourself in visitor pattern-land.
And the other issue with C++ is that it does not allow open functions. In the visitor pattern you necessarily have to implement f(A, B) for _all_ subtypes of A and B.
In Julia open functions + multiple dispatch make life quite a bit easier than in C++:
julia> abstract type Animal end
julia> abstract type Color end
julia> struct Dog <: Animal end
julia> struct Cat <: Animal end
julia> struct Brown <: Color end
julia> struct White <: Color end
julia> f(::White, ::Cat) = println("Hello white cat")
julia> f(::Brown, ::Dog) = println("Hello brown dog")
julia> for (color, animal) in [(White(), Cat()), (Brown(), Dog())]
f(color, animal)
end
Hello white cat
Hello brown dog
Note that array type is here is Vector{Tuple{Color,Animal}}, which would correspond to type erasure in C++.Lastly, the modern C++ version with std::variant + std::visitor is probably not really multiple dispatch, since it has a union storage type under the hood.
Just as an example automatic differentation libraries are written using function overloading (in which the arguments of the overloaded functions are a tuple of a value and its derivative).
Another example is CUDA array support. And the best thing is that you can combine these 2 libraries (and many others that take advantage of operator overloading)
At least in C++, most forms of overloading I see is to enable generic programming.
Of course nobody stops you from naming functions doing different things with the same name, but then again nobody stops you from naming functions badly in the first place. As usual with more powers come more responsibility.