Hone logo
Hone
Problems

Implementing a Simple Effect System in TypeScript

Effect systems provide a powerful way to manage side effects in a purely functional style, allowing for better testability, composability, and reasoning about code. This challenge asks you to implement a basic effect system in TypeScript using generics and type manipulation, enabling you to track and manage side effects in a type-safe manner. This will involve defining effect types, handlers, and a mechanism to run effects.

Problem Description

You are tasked with creating a rudimentary effect system in TypeScript. The core components are:

  1. Effect Types: Define a type Effect<T> that represents a side effect. T represents the type of value produced by the effect.
  2. Handlers: Define a type Handler<E, R> that represents a handler for a specific effect type E. R represents the runtime context (think of it as a dependency injection container). The handler should be a function that takes the runtime context R and returns a promise resolving to the effect's result T.
  3. Runtime: Define a type Runtime<R> that represents the runtime environment. It should have a method run<E>(effect: Effect<T>, handler: Handler<E, R>): Promise<T>. This method executes the effect using the provided handler within the runtime context.
  4. Effectful Function: Define a type Effectful<R, E, T> that represents a function that can produce an effect of type E within a runtime context R and returns a value of type T.

Key Requirements:

  • The system should be type-safe, ensuring that handlers are used for the correct effect types.
  • The runtime should be able to execute effects and handle them appropriately.
  • The system should be generic, allowing for different effect types and runtime contexts.

Expected Behavior:

The Runtime.run method should:

  1. Take an Effect and a Handler as input.
  2. Invoke the Handler with the Runtime context (R).
  3. Return a Promise that resolves to the value returned by the Handler.

Edge Cases to Consider:

  • What happens if an incorrect handler is provided for an effect? (Type safety should prevent this, but consider how it would manifest).
  • How does the runtime context (R) get passed to the handler?
  • How can you ensure that the handler is only called with the correct effect type?

Examples

Example 1:

// Define an effect type for fetching data
interface FetchEffect<T> extends Effect<T> {
  type: 'fetch';
  url: string;
}

// Define a handler for the FetchEffect
const fetchHandler = <R>(runtime: Runtime<R>, effect: FetchEffect<any>): Promise<any> => {
  return fetch(effect.url)
    .then(response => response.json());
};

// Create a runtime (simple example)
class MyRuntime<R> implements Runtime<R> {
  run<E, T>(effect: Effect<T>, handler: Handler<E, R>): Promise<T> {
    return handler(this, effect);
  }
}

const runtime = new MyRuntime<any>();

// Create an effect
const fetchEffect: FetchEffect<any> = { type: 'fetch', url: 'https://jsonplaceholder.typicode.com/todos/1' };

// Run the effect
runtime.run<FetchEffect<any>, any>(fetchEffect, fetchHandler)
  .then(data => {
    console.log(data); // Expected: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
  });

Example 2:

// Another effect type for logging
interface LogEffect extends Effect<void> {
  type: 'log';
  message: string;
}

// Handler for LogEffect
const logHandler = <R>(runtime: Runtime<R>, effect: LogEffect): Promise<void> => {
  console.log(effect.message);
  return Promise.resolve();
};

// Create an effect
const logEffect: LogEffect = { type: 'log', message: 'Hello, world!' };

// Run the effect
runtime.run<LogEffect, void>(logEffect, logHandler)
  .then(() => {
    console.log('Log effect completed'); // Expected: "Hello, world!" printed to console, then this line.
  });

Constraints

  • Type Safety: The solution must be type-safe. Incorrect handlers should result in TypeScript errors.
  • Generics: The solution must utilize generics effectively to handle different effect types and runtime contexts.
  • Promise-based: Effects should be handled using Promises.
  • No external libraries: Do not use external libraries for this challenge. Focus on core TypeScript type manipulation.
  • Runtime Context: The runtime context R should be passed to the handler. It doesn't need to do anything with it, but it must be present.

Notes

  • Think about how to use conditional types and mapped types to define the Handler type.
  • Consider how to ensure that the Handler is only called with the correct Effect type.
  • This is a simplified effect system. Real-world effect systems are often more complex, but this challenge focuses on the core concepts.
  • Start by defining the Effect, Handler, and Runtime types. Then, implement the run method of the Runtime. Finally, create some example effects and handlers to test your implementation.
Loading editor...
typescript