2021-05-02
|~4 min read
|662 words
In languages like Java and C#, function overloading is quite popular. The way I understand it: instead of having multiple functions, you define multiple signatures and then allow the caller to determine which makes most sense for its situation. The benefit is that you only need to have one public method while the method itself handles the complexity of handling the result (i.e. leverage polymorphism).
While this isn’t particularly popular in my experience with Javascript, Typescript does allow it - at least in a limited sense.
I originally came across this through Kent C. Dodds’ post on the topic (the second example in this post was inspired by Kent).
Let’s start with a basic addition function:
function addNumbers(a: number, b: number): number
function addNumbers(a: string, b: string): number
function addNumbers(a: number | string, b: number | string) {
a = typeof a === "number" ? a : Number.parseFloat(a)
b = typeof b === "number" ? b : Number.parseFloat(b)
return a + b
}
console.log(addNumbers(1, 2))
console.log(addNumbers("1", "2"))
I’d argue this isn’t a particularly good example since you can imagine using just the final signature of addNumbers(a: number | string, b: number | string)
— so, why wouldn’t you? Well, with the current implementation if you tried to call addNumbers
with a mix of numbers and strings, you’d get a type error:
Notice that the error specifically says the call would have succeeded if not for the overloads! That’s kind of nifty and suggests there’s power in using overloads to guide consumers!
Kent explored more interesting examples. The first was about an asyncAdd
method (which I’ll explore a bit more below) and the second was related to typing his babel-plugin-macros
.
type asyncAddCb = (result: number) => unknown
function asyncAdd(a: number, b: number): Promise<number>
function asyncAdd(a: number, b: number, cb: asyncAddCb): unknown
function asyncAdd(a: number, b: number, cb?: asyncAddCb) {
const result = a + b
if (cb) return cb(result)
else return Promise.resolve(result)
}
const promised = asyncAdd(1, 2)
.then((res) => res + 5)
.then((res) => console.log(res)) // 8
const withCallBack = asyncAdd(1, 2, (res) => res + 5)
console.log(withCallBack) // 8
Note: In Kent’s implementation,
asyncAddCB
and the second overload forasyncAdd
were typed to returnvoid
. That didn’t make a lot of sense to me since they could return values (and in fact,withCallBack
is assigned the value of 8 in this example). As a result, I updated them to unknown since the callback could return anything, includingvoid
, but at this time, we just don’t know what it will be.
Thinking more about this example, I actually think asyncAdd
is a peculiar name because the callback approach is not necessarily asynchronous. It might have been if we had typed asyncAddCb
like:
type asyncAddCb = (result: number): Promise<number>
This would have been similar to the alternative approach and from the caller’s perspective, the only difference would be whether we pass in a callback or define it in a promise chain. One way to think about this would be:
- type asyncAddCb = (result: number) => unknown
+ type asyncAddCb = (result: number) => Promise<number>
function asyncAdd(a: number, b: number): Promise<number>
- function asyncAdd(a: number, b: number, cb: asyncAddCb): unknown
+ function asyncAdd(a: number, b: number, cb: asyncAddCb): Promise<number>
function asyncAdd(a: number, b: number, cb?: asyncAddCb) {
const result = a + b
if (cb) return cb(result)
else return Promise.resolve(result)
}
const promised = asyncAdd(1,2).then(res => res + 5).then(res => console.log(res))
- const withCallBack = asyncAdd(1,2,(res) => res+5)
+ const withCallBack = asyncAdd(1,2,(res) => Promise.resolve(res+5))
- console.log(withCallBack) // 8
+ withCallBack.then(res => console.log(`withCallBack resolved:`,res))
I’m sure there are a ton of good reasons for function overloading and I’m excited to learn how it’s done with Typescript, though it does seem more limited than other languages (or I haven’t figured out how to make it work). In C# for example, it’s possible to change the order of the parameters.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!