Hone logo
Hone
Problems

TypeScript Strict Null Check Helpers

In modern TypeScript development, leveraging strict null checks (strictNullChecks: true in tsconfig.json) is a powerful way to catch common bugs related to null and undefined values at compile time. However, working with potentially null or undefined values can sometimes lead to verbose conditional checks. This challenge asks you to create utility functions that simplify and enforce strict null checks in a more elegant and readable way.

Problem Description

You are tasked with building a set of TypeScript utility functions that help manage values that could be null or undefined under strict null checks. These helpers should allow for cleaner code when dealing with optional properties, function arguments, or return values that might not be present.

The core requirements are:

  1. requireValue<T>(value: T | null | undefined, errorMessage?: string): T: This function should take a value that could be T, null, or undefined.

    • If the value is not null or undefined, it should return the value itself (typed as T).
    • If the value is null or undefined, it should throw an Error.
    • An optional errorMessage string can be provided. If provided and an error is thrown, this message should be used. If not provided, a default message like "Value is null or undefined" should be used.
  2. unwrapOr<T>(value: T | null | undefined, defaultValue: T): T: This function should take a value that could be T, null, or undefined, and a default value of type T.

    • If the value is not null or undefined, it should return the value itself.
    • If the value is null or undefined, it should return the defaultValue.
  3. unwrapOrElse<T>(value: T | null | undefined, getDefaultValue: () => T): T: This function should take a value that could be T, null or undefined, and a function that returns a T.

    • If the value is not null or undefined, it should return the value itself.
    • If the value is null or undefined, it should call the getDefaultValue function and return its result. This is useful when the default value is expensive to compute or depends on external factors.

Key Considerations:

  • Type Safety: All helper functions must maintain strict TypeScript type safety. The return type should be correctly inferred and enforced.
  • strictNullChecks Compliance: Your solution must work seamlessly when strictNullChecks is enabled in the tsconfig.json. This means the input parameters must accurately reflect the possibility of null or undefined.
  • Clarity and Readability: The goal is to make code that handles optional values more concise and easier to understand.

Examples

Example 1: requireValue

function processUser(user: { name: string } | null | undefined) {
  // Without helper:
  // if (user === null || user === undefined) {
  //   throw new Error("User is missing!");
  // }
  // const userName: string = user.name; // TS error if no check

  // With helper:
  const verifiedUser = requireValue(user, "User object cannot be null or undefined.");
  const userName: string = verifiedUser.name; // No TS error here

  console.log(`Processing user: ${userName}`);
}

// Test cases:
processUser({ name: "Alice" }); // Output: Processing user: Alice
try {
  processUser(null);
} catch (e: any) {
  console.error(e.message); // Output: User object cannot be null or undefined.
}
try {
  processUser(undefined);
} catch (e: any) {
  console.error(e.message); // Output: User object cannot be null or undefined.
}

Example 2: unwrapOr

function getUserPreference(preference: string | null | undefined, defaultPref: string): string {
  // Without helper:
  // return preference === null || preference === undefined ? defaultPref : preference;

  // With helper:
  return unwrapOr(preference, defaultPref);
}

// Test cases:
console.log(getUserPreference("dark", "light")); // Output: dark
console.log(getUserPreference(null, "light"));   // Output: light
console.log(getUserPreference(undefined, "system")); // Output: system

Example 3: unwrapOrElse

function getConfigurationValue(config: string | null | undefined, fallbackConfigProvider: () => string): string {
  // Without helper:
  // if (config !== null && config !== undefined) {
  //   return config;
  // } else {
  //   return fallbackConfigProvider();
  // }

  // With helper:
  return unwrapOrElse(config, fallbackConfigProvider);
}

// Test cases:
const expensiveFallback = () => {
  console.log("Calculating expensive fallback...");
  return "default_setting";
};

console.log(getConfigurationValue("user_setting", expensiveFallback)); // Output: user_setting (fallback not called)
console.log(getConfigurationValue(null, expensiveFallback));      // Output: Calculating expensive fallback...
                                                                // Output: default_setting
console.log(getConfigurationValue(undefined, () => "another_default")); // Output: another_default (fallback called)

Constraints

  • The helper functions must be implemented in TypeScript.
  • No external libraries should be used for these specific helper functions.
  • The functions should be generic (<T>) to work with any type.
  • The implementation should be efficient; no unnecessary computations or object creations.

Notes

  • Consider how TypeScript's type narrowing works and how your functions leverage it.
  • The errorMessage in requireValue should be an optional parameter, meaning it can be omitted.
  • The getDefaultValue function in unwrapOrElse should be a function that returns the default value, not the value itself. This allows for lazy evaluation of the default.
Loading editor...
typescript