Overview
I'm trying to use TypeScript to write the equivalent of an overloaded method in Java, and can't tell if using union types provides any value in this specific case, versus using separate function names. This same concept is relevant in JavaScript (among other languages), where the variable could have any type, rather than two specified types.
Java with Method Overloading
Let's say I want to overload a basic method, such as below.
void expectPickedApples(String treeName, int expectedAppleCount) {
assert(pickApples(treeName) == expectedAppleCount);
}
void expectPickedApples(int treeIndex, int expectedAppleCount) {
expectPickedApples(getTreeName(treeIndex), expectedAppleCount);
}
TypeScript Options
Now let's say I want the same functionality in a language where I can't overload the method, but I'm able to use union types, such as TypeScript. Again, this could apply to JavaScript or another language where the variable type is any.
Different Function Names (Option 1)
I could simply use different function names, such as below:
function expectPickedApplesForTreeName(treeName: string, expectedAppleCount: number) {
expect(pickApples(treeName)).toBe(expectedAppleCount);
}
function expectPickedApplesForTreeIndex(treeIndex: number, expectedAppleCount: number) {
getTreeName(treeIndex).then(treeName => {
expectPickedApplesForTreeName(treeName, expectedAppleCount);
});
}
Union Types (Option 2)
My solution using union types would look like this:
function expectPickedApples(treeIdentifier: string | number, expectedAppleCount: number) {
if (typeof(treeIdentifier) === 'number') {
expect(pickApples(treeIdentifier)).toBe(expectedAppleCount);
} else {
getTreeName(treeIdentifier).then(treeName => {
expect(pickApples(treeName)).toBe(expectedAppleCount);
});
}
}
Question with Reasoning
There are cases where using union types would clearly provide value. However, in this case, I just use an if statement with the type to choose one of two blocks. Which option above is preferred? Is there a better alternative?
Option 1 provides compact functions and testability, but option 2 provides convenience and potential readability.
If I were to only call these function(s) and were blind of the implementation, I would choose option 2, since there's only one function name to remember and the code is simple (could always see method docs to remember parameter meanings).
expectPickedApples('the old tree', 5);
expectPickedApples(3, 10);
instead of
expectPickedApplesForTreeName('the old tree', 5);
expectPickedApplesForTreeIndex(3, 10);
However, if I were to own the implementation of this code, including responsibilities like testing, option 1 seems better. For example, it would be easier to identify the location of a bug, such as if the treeIndex needs modification before getting the name or getting the tree name fails (getting the name should have its own tests, but maybe it's a call to a service I might not trust).