sweet-monads

repository·master·Indexed 43 days ago

https://github.com/jsmonk/sweet-monads

A modular suite of lightweight, dependency-free monad implementations for TypeScript and JavaScript version 3.3.0. It provides type-safe functional programming constructs including the Either monad for error handling, the Identity monad for functional chaining, and a set of interfaces for implementing Functor, Applicative, and Monad patterns.

Tokens
21.4K
Snippets
94
Records
108
Agent score
55%

What's inside sweet-monads

  1. Overview of @sweet-monads

    master

    @sweet-monads is a collection of easy-to-use monad implementations designed with static type definitions. The project is modular, providing separated packages for different monad types to keep implementations lightweight. Key characteristics include:

    • Zero dependencies: Each package is small and self-contained.
    • Auditable: The TypeScript/JS code is designed to be easily readable and auditable.
    • Modular: You can install only the specific monad implementations you need.
  2. What is the Identity Monad?

    master
    The Identity monad is a monad that does not embody any computational strategy. It simply applies the bound function to its input without any modification. It is useful for wrapping values in a monadic context to enable functional chaining (map, chain, etc.) without changing the underlying value.
  3. What is the Either monad?

    master
    The Either monad is a functional programming construct used to represent a value that can be one of two types. By convention, a value of type Either a b is either a Left a (representing an error or alternative path) or a Right b (representing the primary successful value). This allows for composable error handling and cleaner control flow in TypeScript.
  4. Use LazyIterator for efficient collection processing

    master

    The @sweet-monads/iterator library provides a LazyIterator class that implements lazy helper methods. Unlike standard array methods that create intermediate arrays and iterate multiple times, LazyIterator methods (except those returning non-LazyIterator values) are lazy. They only consume the underlying iterator when a terminal operation is called, significantly reducing the number of iterations required for chained operations.

    import LazyIterator from "@sweet-monads/iterator";
    
    // array from 1 to 10000
    const longArray = Array.from({ length: 10000 }).map((_, i) => i + 1);
    const lazyArray = LazyIterator.from<number>(longArray);
    
    // ~ 25_000 iterations (standard array approach)
    const sum1 = longArray
      .filter(i => i % 2)
      .map(i => i * i)
      .filter(i => i % 3)
      .reduce((sum, i) => sum + i, 0);
    
    // 10_000 iterations (lazy approach)
    const sum2 = lazyArray
      .filter(i => i % 2)
      .map(i => i * i)
      .filter(i => i % 3)
      .sum();
  5. Available @sweet-monads libraries

    master

    The @sweet-monads ecosystem is split into several specialized packages. Depending on your requirements, you can use one or more of the following:

    • Maybe: For handling optional values and avoiding null/undefined checks.
    • Either: For handling computations that can result in one of two values (typically a success or a failure).
    • Iterator: For monadic operations over sequences/iterators.
    • Identity: A basic identity monad.
    • Interfaces: Provides the core type definitions and interfaces used across the library ecosystem.
  6. Use AsyncChainable for easier Promise integration

    master

    AsyncChainable is a static interface that allows AsyncMonad implementations to be used more comfortably with standard JavaScript Promises. It is designed to be used with the @ClassImplements decorator.

    Method:

    • chain<A, B>(f: (v: A) => Promise<M & AsyncMonad<B>>): (m: M & AsyncMonad<A>) => Promise<M & AsyncMonad<B>>

    Example Usage:

    @ClassImplements<IdentityMonad<unknown>>
    class IdentityMonad<T> extends AsyncMonad<T> { /*...*/ }
    
    declare function getAsyncValue(): Promise<IdentityMonad<number>>
    declare function sendToServer(value: number): Promise<IdentityMonad<void>>
    
    const value = await getAsyncValue().then(chain(sendToServer));
  7. How LazyIterator handles infinite/cycled sequences

    master

    A LazyIterator can be set to isCycled mode via the .cycle() method. This allows the iterator to restart from its source once it reaches the end, effectively creating an infinite sequence.

    Constraints for Cycled Iterators:

    1. Terminal Operations: Methods like .forEach() will loop infinitely if the iterator is cycled and the loop is not manually terminated via a Maybe.none() return. Always use .take(n) or .takeWhile() to bound a cycled iterator.
    2. Incompatible Operations: Certain operations that require knowing the full length of the sequence (like .reverse()) will throw an error if called on a cycled iterator.
    3. Termination: Methods like .slice(start, end) or .take(n) are designed to work with cycled iterators by providing a termination condition.
    const infinite = LazyIterator.from([1, 2, 3]).cycle();
    
    // Use take() to prevent an infinite loop in forEach
    infinite.take(10).forEach(console.log); // 1, 2, 3, 1, 2, 3, 1, 2, 3, 1