Hone logo
Hone
Problems

TypeScript Type Narrowing Utilities

In TypeScript, type narrowing is a powerful technique that allows the compiler to infer more specific types within certain code blocks. This leads to safer and more expressive code. This challenge asks you to create a set of utility functions that leverage and enhance type narrowing capabilities.

Problem Description

Your task is to implement several generic utility functions in TypeScript that help with type narrowing. These functions should allow developers to elegantly check for and extract specific types from union types or mixed data structures.

Key Requirements:

  1. isType<T>(value: any): value is T:

    • This function should take a value and a type parameter T.
    • It should return true if the value is of type T and false otherwise.
    • Crucially, it must use a type predicate (value is T) so that TypeScript can narrow down the type of value within conditional blocks where this function returns true.
    • This is a foundational utility, so consider how to make it as general and robust as possible for primitive types and potentially object shapes.
  2. assertType<T>(value: any, errorMessage?: string): asserts value is T:

    • Similar to isType, but instead of returning false, it should throw an error if the value is not of type T.
    • It should accept an optional errorMessage string. If not provided, a default error message should be used.
    • This function must also use a type predicate (asserts value is T) for type narrowing.
  3. filterByType<T>(values: any[]): T[]:

    • This function should take an array of mixed types (any[]).
    • It should return a new array containing only the elements from the input array that are of type T.
    • This function should internally leverage the isType utility to perform the filtering and type checking.

Expected Behavior:

  • The utilities should correctly identify primitive types (string, number, boolean, null, undefined, symbol, bigint).
  • They should also be capable of narrowing down to object types, though the definition of "object type" might be constrained by the nature of runtime JavaScript checks.
  • The type predicates should enable compile-time type safety.

Edge Cases to Consider:

  • null and undefined: How are these handled by type checks?
  • Arrays: How do you check if something is an array?
  • Objects: How do you check for specific object structures (beyond just typeof obj === 'object')? For this challenge, focus on basic typeof checks and array checks for isType and assertType. filterByType should work with these.
  • Passing undefined as T to isType or assertType.

Examples

Example 1:

// Using isType
let data: string | number = "hello";

if (isType<string>(data)) {
  // data is narrowed to string here
  console.log(data.toUpperCase()); // OK
}

// Using assertType
function processValue(val: string | number) {
  assertType<string>(val, "Expected a string");
  // val is narrowed to string here
  console.log(val.length); // OK
}

try {
  processValue(123);
} catch (e: any) {
  console.error(e.message); // Output: Expected a string
}

Explanation: isType<string>(data) returns true, allowing TypeScript to treat data as string within the if block. assertType<string>(val, "Expected a string") checks if val is a string; since it's a number, it throws an error with the specified message.

Example 2:

// Using filterByType
const mixedArray: (string | number | boolean)[] = [
  "apple",
  123,
  true,
  "banana",
  456,
  false,
];

const stringsOnly: string[] = filterByType<string>(mixedArray);
console.log(stringsOnly); // Output: [ 'apple', 'banana' ]

const numbersOnly: number[] = filterByType<number>(mixedArray);
console.log(numbersOnly); // Output: [ 123, 456 ]

Explanation: filterByType<string> iterates through mixedArray, uses an internal type check (presumably isType) to identify strings, and returns a new array containing only those strings. Similarly for filterByType<number>.

Example 3:

// Edge case: null and undefined
let maybeNull: string | null = null;
let maybeUndefined: number | undefined = undefined;

if (isType<string>(maybeNull)) {
  console.log("This won't be printed");
} else {
  console.log("maybeNull is not a string"); // Output: maybeNull is not a string
}

try {
  assertType<number>(maybeUndefined, "Value should be a number");
} catch (e: any) {
  console.error(e.message); // Output: Value should be a number
}

// Edge case: Array
const myArray: unknown[] = [1, "two", [3, 4]];
const arrOnly: number[][] = filterByType<number[]>(myArray); // This might not work as expected with basic typeof checks
console.log(arrOnly); // Expected: [] (if basic checks are used)

Explanation: isType<string>(null) correctly returns false. assertType<number>(undefined) throws an error. The array example highlights the limitations of basic runtime checks if deep structural typing is required; for this challenge, focus on Array.isArray for arrays and typeof for primitives.

Constraints

  • Your solution must be written entirely in TypeScript.
  • The primary goal is to demonstrate correct type predicate usage and generic utility function design. Performance is not a critical concern for this challenge.
  • The isType and assertType functions should primarily rely on JavaScript's built-in runtime type checking mechanisms (typeof, instanceof, Array.isArray). Avoid complex runtime reflection libraries.
  • The filterByType function should be generic enough to work with any type T that can be checked by isType.

Notes

  • Remember that JavaScript's typeof null returns "object". You'll need to handle this specific case.
  • Consider how you will check for arrays. Array.isArray() is the standard JavaScript way.
  • For isType<T> and assertType<T>, the implementation will largely depend on how you map TypeScript types to JavaScript runtime checks. For example, T being string might map to typeof value === 'string'.
  • Think about the return types of your functions, especially the use of value is T and asserts value is T. This is where the magic of type narrowing happens!
Loading editor...
typescript