Hone logo
Hone
Problems

Refinement Types: Building a Custom Type System in TypeScript

Refinement types allow you to create new types by narrowing down existing ones based on specific conditions. This challenge asks you to implement a simplified version of refinement types, enabling you to define types that only accept objects that satisfy certain properties. This is a powerful technique for improving type safety and code clarity, especially when dealing with complex data structures.

Problem Description

You are tasked with creating a function Refine<T, Condition> that takes a generic type T and a Condition type (a type predicate) as input. The function should return a new type that represents T but only includes objects that satisfy the Condition. The Condition type will be a function that takes an object of type T and returns a boolean. If the condition is true, the object is considered valid; otherwise, it's invalid.

Key Requirements:

  • Type Safety: The resulting type should be strictly typed and only allow objects that satisfy the provided condition.
  • Generic Types: The solution must utilize generic types to handle various input types.
  • Condition Type: The Condition type must be a function that accepts an object of type T and returns a boolean.
  • No Runtime Execution: The solution should be purely a type-level operation; it should not involve any runtime execution of the Condition function.

Expected Behavior:

Given a type T and a condition Condition, Refine<T, Condition> should produce a new type that filters T to include only objects that satisfy Condition.

Edge Cases to Consider:

  • Empty types: What happens if T is never?
  • Complex conditions: The condition might involve multiple properties of the object.
  • Type compatibility: Ensure the resulting type is compatible with the original type when the condition is always true.

Examples

Example 1:

type Person = {
  name: string;
  age: number;
  city: string;
};

type YoungPerson = {
  age: number;
};

type Adults = Refine<Person, (p: Person) => p.age >= 18>;

// Adults is now: { name: string; age: number; city: string; } & { age: number }
// which is equivalent to: { name: string; age: number; city: string; }

Example 2:

type Product = {
  id: string;
  name: string;
  price: number;
  isAvailable: boolean;
};

type AvailableProduct = Refine<Product, (p: Product) => p.isAvailable>;

// AvailableProduct is now: { id: string; name: string; price: number; isAvailable: boolean; } & { isAvailable: true; }
// which is equivalent to: { id: string; name: string; price: number; isAvailable: true; }

Example 3:

type Point = {
  x: number;
  y: number;
};

type PositivePoint = Refine<Point, (p: Point) => p.x > 0 && p.y > 0>;

// PositivePoint is now: { x: number; y: number; } & { x: number; y: number; } & (p: Point) => p.x > 0 && p.y > 0
// which is equivalent to: { x: number; y: number; } & { x: number; y: number; }

Constraints

  • The Condition type must be a function that accepts an object of type T and returns a boolean.
  • The solution must be purely type-level; no runtime execution is allowed.
  • The resulting type should be as specific as possible, reflecting the condition applied.
  • The solution should handle the case where the condition is always true gracefully (i.e., return the original type).

Notes

  • This challenge focuses on the type-level implementation of refinement types. You'll need to leverage conditional types and intersection types to achieve the desired behavior.
  • Consider how to represent the condition as a type-level predicate.
  • Think about how to combine multiple conditions if needed (although this is not explicitly required for this challenge).
  • The goal is to create a type-level function that transforms a type based on a condition, not to validate data at runtime.
Loading editor...
typescript