I have prepared a little function called reverse, which takes either a string or an array, and then returns a reversed copy of that string or array. If we got a string, we first break it up into an array of all of its code points, which we then reverse and join back together into a new string. Otherwise, we must have gotten an array as an input parameter.
We go ahead and create a copy of this array using the slice method, and then call reverse on that copied array. Notice that it's very important to call slice first, because the reverse method directly modifies the array it operates on. If we didn't call slice, we would actually change our input parameter, and our function would no longer be pure.
Let me now go ahead and call the reverse method with a string, and let me also call it with an array. Both variables are inferred to be of type string or any array. This is because the reverse function accepts a string or an any array and returns a string or an any array.
Now, we, as a developer, know that if we pass a new string here, we'll get back a string, and if we pass in an array, we'll get back an array. That's not encoded in the type system. The signature of the reverse function only tells us that it accepts a string or an any array and returns a string or an any array, but nowhere does it correlate the two.
It doesn't say that given a string, it'll give back a string, and given an any array, it'll give back an any array. This is where function overlords come into play, because they allow us to state exactly that.
Right before the function implementation, we're going to add two so-called overlord signatures. They allow us to say what combinations of parameter and return types are valid for the following function.
In our case, we either take a string and give back a string, or we take an any array and give back an any array. Reversed string is now typed as a string, and reversed array is now typed as an any array.
The compiler can now infer these types because it knows that when we pass in a string, we want to get back a string, and if we pass in an array of numbers, or in fact any array, we want to get back any array.
We could give even more precise information if we made this overlord generic. Let's introduce a generic type, T, and replace the any by the T. Now, if we hover over the reversed array variable, we see that it's typed as a number array. We no longer have to deal with any.
We're still using the any array within the actual implementation of the function, but that's not a huge problem, because the only thing we are exposing to the outside world are these two overlords. The only requirement is that the types within the overlords are compatible with the types in the implementation.
If I were to change the string to a number here, the first overlord would no longer be compatible with the actual implementation, so we cannot do that.
The cool thing about these function overlords is that we can rename the parameters, and even the JS stack, for every single occurrence. Here, for example, I have copied the JS stack, and I can now be more specific. I can describe in detail what every overlord does, and I can even use better parameter names.
In this overlord, for example, I'll call the parameter array, and in the overlord above, I can simply call it string.
The TypeScript language service can now present us with the correct documentation when we hover over the functions. For example, here it says, "Reverses the given string," while below, it says, "Reverses the given array."
One word of caution. You want to be careful with the types that you specify in your function overlords, because the compiler does not check that they are, in fact, correct for every code path.
For example, if you said that reverse always returns a string, your entire program would still be considered type-correct, although that statement is not true. Down here, reversed array would now also be typed as a string, and no longer as a number array, but that's clearly not the behavior of the reverse function.
You want to be careful and make sure that your type declarations in your overlords match the actual implementation.