This particular inquiry sheds light on certain limitations of TypeScript. To put it simply: achieving what you're attempting may not be feasible, at least not in the manner you are pursuing. (A discussion regarding this issue took place within the question's comments.)
For maintaining type safety, TypeScript predominantly depends on compile-time type validation. Unlike JavaScript, identifiers in TypeScript possess a specific type; sometimes explicitly designated, other times inferred by the compiler. In essence, if you attempt to treat an identifier as a different type than its known one, the compiler will raise objections.
This creates obstacles when integrating with existing JavaScript libraries since JavaScript identifiers do not carry types. Additionally, determining the type of a value during runtime is unreliable.
These challenges gave rise to the concept of type guards. It involves creating a function in TypeScript designed to inform the compiler that if the function yields true, one of its passed arguments is definitively a specific type. This approach allows for implementing custom duck typing to serve as a mediator between JavaScript and TypeScript. A typical type guard function appears like so:
const isDuck = (x: any): x is Duck => looksLikeADuck(x) && quacksLikeADuck(x);
While not an optimal solution, it proves effective when vigilant about type checking techniques, presenting few alternative options.
Nevertheless, type guards exhibit limitations with generic types. Recall, the aim of a type guard is to receive an any
input and ascertain its type. Progressing partially with generic types may appear as follows:
function isList(list: any): list is List<any> {
if (isNil(list)) {
return false;
}
return "head" in list && "tail" in list;
}
Nonetheless, this scenario remains suboptimal. While able to test for a List<any>
, assessing a more specific type such as
List<number></code could prove challenging—without that capability, outcomes might not offer much utility given encapsulated values retain unknown types.</p>
<p>The primary goal entails verifying whether something qualifies as a <code>List<T>
. This endeavor introduces a level of complexity:
function isList<T>(list: any): list is List<T> {
if (isNil(list)) {
return false;
}
if (!("head" in list && "tail" in list)) {
return false;
}
return isT<T>(list.head) && (isNil(list.tail) || isList<T>(list.tail));
}
Hence, defining isT<T>
emerges as essential:
function isT<T>(x: any): x is T {
// What goes here?
}
Regrettably, executing this task stands unattainable. The absence of a method to validate at runtime whether a value represents an arbitrary type hinders progress. An unconventional workaround can somewhat address this challenge:
function isList<T>(list: any, isT: (any) => x is T): list is List<T> {
if (isNil(list)) {
return false;
}
if (!("head" in list && "tail" in list)) {
return false;
}
return isT(list.head) && (isNil(list.tail) || isList<T>(list.tail, isT));
}
Consequently, accountability shifts to the caller:
function isListOfNumbers(list: any): list is List<number> {
return isList<number>(list, (x): x is number => typeof x === "number");
}
The abovementioned solutions remain less than ideal. Whenever possible, evading this approach is recommended in favor of relying on TypeScript's robust type verification system. Although exemplars were provided, initially addressing the definition of List<T>
is crucial:
type List<T> = Readonly<null | {
head: T;
tail: List<T>;
}>;
Therefore, instead of:
function sum(list: any) {
if (!isList<number>(list, (x): x is number => typeof x === "number")) {
throw "Panic!";
}
// list is now a List<number>--except if inaccuracies exist in our isList or isT implementations.
let result = 0;
for (let x = list; x !== null; x = x.tail) {
result += list.head;
}
return result;
}
Consider utilizing:
function sum(list: List<number>) {
// list embodies a List<number>--unless directly invoked from JavaScript.
let result = 0;
for (let x = list; x !== null; x = x.tail) {
result += list.head;
}
return result;
}
In cases involving library creation or interaction with raw JavaScript code, strict type checks may not always be achievable universally. Nonetheless, striving towards leveraging them whenever viable is advised.