Hone logo
Hone
Problems

Implement a Generic Shift Type in TypeScript

This challenge requires you to create a robust and type-safe Shift type in TypeScript. This type will be used to represent data that might have a value or might be in a "shifted" or "pending" state, which is common in scenarios like asynchronous operations, feature flags, or data that is being processed.

Problem Description

You need to define a generic TypeScript type called Shift. This type should represent a value that can either be the actual data of a specific type T, or it can be a representation of a "shifted" or "pending" state. The goal is to create a type that clearly distinguishes between these two possibilities, ensuring type safety and improving code readability when dealing with such states.

Key requirements:

  • Generic: The Shift type must be generic, accepting a type parameter T for the underlying data.
  • Union Type: It should be a union type that can hold either T or a specific "shifted" state.
  • Distinguishable States: The "shifted" state should be clearly distinguishable from a regular value of type T.
  • Type Safety: Operations performed on a Shift<T> should enforce checking which state it's in before accessing the value.

Expected behavior:

A Shift<T> can represent:

  1. A concrete value of type T.
  2. A distinct "shifted" state, which does not hold a value of T.

Consider these edge cases:

  • What if T itself is a union type?
  • How will you handle null or undefined as valid T values?

Examples

Example 1:

type Shift<T> = T | { type: 'shifted' };

let myValue: Shift<number> = 10;
let myShiftedValue: Shift<number> = { type: 'shifted' };

// To access the value, you would typically check the type:
if (typeof myValue === 'number') {
  console.log(myValue.toFixed(2)); // Outputs: 10.00
}

if (myShiftedValue.type === 'shifted') {
  console.log("It's in a shifted state."); // Outputs: It's in a shifted state.
}

Explanation: This example shows how Shift<number> can hold either a number or an object with a specific type property indicating the "shifted" state. Accessing the value requires a type guard.

Example 2:

type Shift<T> = T | { type: 'pending', message?: string };

let userProfile: Shift<{ name: string; age: number }> = {
  name: "Alice",
  age: 30
};

let pendingProfile: Shift<{ name: string; age: number }> = {
  type: 'pending',
  message: 'Profile data loading...'
};

// Type guard to differentiate
function isShifted<T>(value: Shift<T>): value is { type: 'pending', message?: string } {
  return typeof value === 'object' && value !== null && 'type' in value && value.type === 'pending';
}

if (!isShifted(userProfile)) {
  console.log(`User: ${userProfile.name}, Age: ${userProfile.age}`); // Outputs: User: Alice, Age: 30
}

if (isShifted(pendingProfile)) {
  console.log(`Status: ${pendingProfile.message}`); // Outputs: Status: Profile data loading...
}

Explanation: This example demonstrates a more complex "shifted" state with an optional message property. A custom type guard isShifted is introduced for better readability when checking the state.

Example 3: (Edge case with null/undefined)

type Shift<T> = T | { type: 'cleared' };

let optionalValue: Shift<string | null> = null;
let clearedValue: Shift<string | null> = { type: 'cleared' };

if (optionalValue === null) {
  console.log("Value is null."); // Outputs: Value is null.
}

if (optionalValue !== null && typeof optionalValue === 'string') {
  console.log(`String value: ${optionalValue.toUpperCase()}`);
}

if (typeof clearedValue === 'object' && clearedValue !== null && clearedValue.type === 'cleared') {
  console.log("Value has been cleared."); // Outputs: Value has been cleared.
}

Explanation: This shows how Shift<T> correctly handles T being string | null. The "shifted" state (cleared) is distinct from null.

Constraints

  • The Shift type must be defined within a single TypeScript file.
  • The solution should leverage TypeScript's built-in type system without relying on external libraries.
  • The type should be efficient and not introduce significant runtime overhead if used in conjunction with JavaScript.

Notes

  • Consider how you will represent the "shifted" state. A literal object with a specific type property is a common and effective pattern for discriminated unions.
  • Think about how a consumer of your Shift type would safely access the underlying value T. Type guards are essential here.
  • The goal is to create a reusable and type-safe abstraction.
Loading editor...
typescript