Hone logo
Hone
Problems

Mastering Non-Distributive Wrappers in TypeScript

Understanding and implementing non-distributive wrappers is a crucial skill for building robust and predictable functional programming paradigms in TypeScript. This challenge focuses on creating a wrapper that does not distribute operations across its contained values, ensuring that computations are applied to the wrapper as a whole, rather than individually to its elements. This is vital for scenarios where side effects or specific contextual operations need to be maintained.

Problem Description

Your task is to design and implement a generic TypeScript class called NonDistributiveWrapper<T> that encapsulates a single value of type T. This wrapper should provide methods to perform common operations, but critically, these operations must not automatically apply to the inner value T. Instead, the operations should act upon the wrapper instance itself.

Specifically, you need to implement the following:

  1. Encapsulation: The wrapper must hold a private instance of the value T.
  2. map Method: Implement a map method that takes a function (value: T) => U and returns a new NonDistributiveWrapper<U>. Crucially, this map should transform the inner value T into U within the context of a new wrapper. It should not directly return the transformed value U.
  3. flatMap Method: Implement a flatMap method that takes a function (value: T) => NonDistributiveWrapper<U> and returns a new NonDistributiveWrapper<U>. This method should unwrap the inner value, apply the function to it, and then return the resulting NonDistributiveWrapper<U> directly, without further nesting.
  4. getValue Method: A public method to retrieve the raw, unwrapped value of type T held within the wrapper.

Key Requirements:

  • The wrapper should be immutable. All operation methods (map, flatMap) must return new instances of NonDistributiveWrapper.
  • The map and flatMap operations are the core of this challenge, demonstrating the non-distributive nature.

Expected Behavior:

  • When map is called, the provided function operates on the inner T, and the result is placed inside a new NonDistributiveWrapper.
  • When flatMap is called, the provided function operates on the inner T, and the entire NonDistributiveWrapper returned by that function is returned.

Edge Cases:

  • Consider how null or undefined might be handled as inner values if T allows for it. The wrapper should preserve these values correctly.

Examples

Example 1:

// Assume NonDistributiveWrapper<T> is implemented

// Create a wrapper for a number
const numberWrapper = new NonDistributiveWrapper<number>(10);

// Map a function to add 5 to the inner value
const mappedWrapper = numberWrapper.map(x => x + 5);

// Get the value from the new wrapper
const mappedValue = mappedWrapper.getValue(); // Should be 15

// Get the value from the original wrapper
const originalValue = numberWrapper.getValue(); // Should be 10

Output:

mappedValue: 15
originalValue: 10

Explanation: The map operation transformed the inner 10 to 15 and placed it in a new NonDistributiveWrapper. The original wrapper remained unchanged.

Example 2:

// Assume NonDistributiveWrapper<T> is implemented

// Create a wrapper for a string
const stringWrapper = new NonDistributiveWrapper<string>("hello");

// FlatMap a function that returns a new wrapper
const flatMappedWrapper = stringWrapper.flatMap(s =>
    new NonDistributiveWrapper<string>(s.toUpperCase() + "!")
);

// Get the value from the new wrapper
const flatMappedValue = flatMappedWrapper.getValue(); // Should be "HELLO!"

// Get the value from the original wrapper
const originalStringValue = stringWrapper.getValue(); // Should be "hello"

Output:

flatMappedValue: "HELLO!"
originalStringValue: "hello"

Explanation: The flatMap operation took the inner string "hello", applied the function to get "HELLO!", and then the function returned a new NonDistributiveWrapper containing "HELLO!". This returned wrapper was directly used as the result of flatMap.

Example 3: (Handling null as an inner value)

// Assume NonDistributiveWrapper<T> is implemented

const nullWrapper = new NonDistributiveWrapper<string | null>(null);

const mappedNullWrapper = nullWrapper.map(s => s ? s.length : 0);
const mappedNullValue = mappedNullWrapper.getValue(); // Should be 0

const flatMappedNullWrapper = nullWrapper.flatMap(s =>
    s === null
        ? new NonDistributiveWrapper<number>(0)
        : new NonDistributiveWrapper<number>(s.length)
);
const flatMappedNullValue = flatMappedNullWrapper.getValue(); // Should be 0

Output:

mappedNullValue: 0
flatMappedNullValue: 0

Explanation: The wrapper correctly holds null, and the map and flatMap operations handle it gracefully, producing the expected transformed values within new wrappers.

Constraints

  • The NonDistributiveWrapper class must be generic, accepting any type T.
  • The map and flatMap methods must preserve immutability, always returning new instances.
  • The getValue method should return the raw T.
  • Performance is not a primary concern for this challenge, but clear and idiomatic TypeScript is expected.

Notes

Think carefully about the return types of map and flatMap. The core idea of a non-distributive wrapper is that operations don't "flatten" or automatically apply to the inner value in a way that would eliminate the wrapper context. map transforms the inner value and re-wraps it, while flatMap expects the function to return a wrapper and uses that result directly. This pattern is fundamental to understanding monads and other functional programming concepts.

Loading editor...
typescript