Hone logo
Hone
Problems

Implementing the Linear Types Pattern in TypeScript

The linear types pattern, inspired by functional programming and linear logic, ensures that a value is used exactly once. This pattern is useful for resource management, ensuring that things like file handles or network connections are properly closed or released after use, preventing leaks and errors. This challenge asks you to implement a basic linear types pattern in TypeScript using generics and type guards.

Problem Description

You need to create a Linear<T> type and a corresponding useLinear<T>(value: T) function. The Linear<T> type should represent a value that can only be consumed once. The useLinear<T>(value: T) function should accept a value of type T, consume it (effectively removing it from further use), and return a result of type U. After a Linear value has been "used" via useLinear, any attempt to access it directly should result in a compile-time error (or, at the very least, a runtime error indicating it has already been consumed).

The core challenge is to enforce this "use-exactly-once" constraint in a type-safe manner. You'll need to leverage TypeScript's type system to track whether a Linear value has been consumed. A simple approach involves wrapping the value in an object that tracks consumption status.

Key Requirements:

  • Linear<T> Type: Represents a value that can be used only once.
  • useLinear<T, U>(value: T): U Function: Consumes the Linear<T> value and returns a value of type U. After calling useLinear, the original Linear<T> value should be considered "consumed" and inaccessible.
  • Type Safety: The compiler should ideally flag attempts to access a consumed Linear value as an error. At a minimum, runtime checks should prevent further use.
  • Generics: The solution must use generics to handle different types.

Expected Behavior:

  1. Creating a Linear<T> should be straightforward.
  2. Calling useLinear<T, U>(linearValue) should consume the linearValue and return a value of type U.
  3. Attempting to access the original value of a Linear<T> after it has been passed to useLinear should result in an error (either compile-time or runtime).

Edge Cases to Consider:

  • What happens if useLinear is never called? (Ideally, this should be detectable or lead to a warning).
  • How to handle different return types U from useLinear.
  • How to prevent accidental re-use of the Linear value.

Examples

Example 1:

Input:
const linearNumber = { value: 10, consumed: false };

function useLinearNumber<U>(linear: { value: number; consumed: boolean }): U {
  if (linear.consumed) {
    throw new Error("Linear value already consumed");
  }
  linear.consumed = true;
  return linear.value * 2 as U;
}

const doubled = useLinearNumber(linearNumber);
console.log(doubled); // Output: 20

// Attempting to access linearNumber.value after useLinearNumber
// would ideally result in a compile-time error or a runtime error.

Output: 20 Explanation: useLinearNumber consumes the linearNumber object, sets consumed to true, and returns the doubled value.

Example 2:

Input:
const linearString = { value: "hello", consumed: false };

function useLinearString<U>(linear: { value: string; consumed: boolean }): U {
  if (linear.consumed) {
    throw new Error("Linear value already consumed");
  }
  linear.consumed = true;
  return linear.value.toUpperCase() as U;
}

const upperCased = useLinearString(linearString);
console.log(upperCased); // Output: HELLO

// Attempting to access linearString.value after useLinearString
// would ideally result in a compile-time error or a runtime error.

Output: HELLO Explanation: useLinearString consumes the linearString object, sets consumed to true, and returns the uppercase version of the string.

Example 3: (Edge Case)

Input:
const linearBoolean = { value: true, consumed: false };

function useLinearBoolean<U>(linear: { value: boolean; consumed: boolean }): U {
  if (linear.consumed) {
    throw new Error("Linear value already consumed");
  }
  linear.consumed = true;
  return !linear.value as U;
}

// const negated = useLinearBoolean(linearBoolean); // This line would throw an error if uncommented.
// console.log(linearBoolean.value); // This line would throw an error if uncommented.

Output: (No output if the commented lines are not executed) Explanation: This demonstrates the intended behavior of preventing access to the linearBoolean after it has been consumed by useLinearBoolean. Uncommenting either line would result in a runtime error.

Constraints

  • The solution must be written in TypeScript.
  • The useLinear function must be generic over both the input type T and the output type U.
  • The solution should prioritize type safety, aiming for compile-time error detection where possible. Runtime checks are acceptable as a fallback.
  • The solution should be reasonably performant. Avoid unnecessary overhead.
  • The Linear<T> type should be a simple object with a value property of type T and a consumed property of type boolean.

Notes

  • Consider using a type guard to help enforce the "use-exactly-once" constraint.
  • Think about how to handle the case where useLinear is never called. While a compile-time error might be difficult to achieve, a runtime check or warning could be helpful.
  • This is a simplified implementation of the linear types pattern. Real-world implementations might be more complex, but this challenge focuses on the core concepts.
  • Focus on demonstrating the core principle of ensuring a value is used exactly once. Error handling and edge case coverage are important, but the primary goal is to implement the linear types pattern.
Loading editor...
typescript