pratica

repository·master·Indexed 19 days ago

https://github.com/rametta/pratica

A functional programming library for pragmatists (version 2.3.0) that balances FP principles with simplicity. It provides algebraic data types, specifically the Maybe and Result monads, to ensure data integrity and safety. The library includes utilities for safe date parsing, safe function execution via encase and encaseRes, safe nested property access, and safe array boundary operations (head, last, tail).

Tokens
6.7K
Snippets
30
Records
32
Agent score
64%

What's inside pratica

  1. Overview of Pratica

    master
    Pratica is a functional programming library designed for pragmatists. It prioritizes a simple and approachable API to allow developers to achieve goals quickly, while maintaining data integrity and safety through the use of algebraic data types.
  2. How the Maybe monad works

    master

    The Maybe monad is used to handle nullable or unreliable data safely, preventing runtime errors caused by null or undefined. A Maybe can be one of two types:

    1. Just: Contains the available data.
    2. Nothing: Represents missing data.

    Most operations on a Maybe will "short-circuit": if the type is Nothing, subsequent transformations like .map() or .chain() are skipped until you handle the empty state using .cata().

    import { nullable, Just, Nothing } from "pratica"
    
    const person = { name: "Jason", age: 4 }
    
    // Successful chain
    nullable(person)
      .map((p) => p.age)
      .cata({
        Just: (age) => console.log(age), // 9
        Nothing: () => console.log(`This won't run`),
      })
    
    // Short-circuited chain
    nullable(null)
      .map((p) => p.age) // Skipped
      .cata({
        Just: (age) => console.log(age),
        Nothing: () => console.log("Missing data"), // Runs
      })
  3. How the Result monad works

    master

    The Result monad is used for handling conditional logic and error states, serving as a functional alternative to if/else or try/catch blocks. A Result is either:

    1. Ok: Contains the successful value.
    2. Err: Contains error information (e.g., a message or error object).

    Methods like .chain() will stop execution and immediately trigger the Err handler if any step in the chain returns an Err.

    import { Ok, Err } from "pratica"
    
    const isPerson = (p) => (p.name && p.age ? Ok(p) : Err("Not a person"))
    const isOlderThan2 = (p) => (p.age > 2 ? Ok(p) : Err("Not older than 2"))
    
    Ok({ name: "Jason", age: 4 })
      .chain(isPerson)
      .chain(isOlderThan2)
      .cata({
        Ok: (p) => console.log("Success"),
        Err: (msg) => console.log(msg), // If any step fails, the first Err is passed here
      })
  4. Install Pratica via npm, yarn, or bun

    master

    You can install Pratica using your preferred package manager. It is designed for functional programming with a focus on simplicity and algebraic data types.

    bun i pratica
    # or
    yarn add pratica
    # or
    npm i pratica
  5. Use the Result monad for error handling

    master

    The Result<O, E> monad is used to represent the outcome of an operation that can either succeed with a value of type O (Ok) or fail with an error of type E (Err). It provides functional methods to transform values, chain operations, and handle errors without explicit try/catch blocks.

    Core Methods

    • map(cb): Transforms the success value using cb. If the result is an error, it remains unchanged.
    • mapErr(cb): Transforms the error value using cb. If the result is a success, it remains unchanged.
    • chain(cb): Chains a new operation that returns a Result. Used for sequential operations where the next step depends on the success of the previous one.
    • chainErr(cb): Chains an operation that returns a Result specifically for the error case.
    • bimap(ok, err): Transforms both the success and error values in a single pass.
    • cata(obj): A catamorphism that collapses the Result into a single value by providing handlers for both Ok and Err cases.
    • toMaybe(): Converts the Result into a Maybe type.
    • isOk() / isErr(): Predicates to check the state of the result.
    • value(): Returns the underlying value (either O or E).
    import { Ok, Err } from './result';
    
    // Success case
    const success = Ok("data");
    const mapped = success.map(x => x.length);
    
    // Error case
    const failure = Err("error message");
    const mappedErr = failure.map(x => x.length); // Still Err("error message")
    
    // Chaining
    const result = Ok(10)
      .chain(x => Ok(x * 2))
      .chain(x => Err("failed")); // Result is Err("failed")
    
    // Folding with cata
    const output = result.cata({
      Ok: (val) => `Success: ${val}`,
      Err: (err) => `Error: ${err}`
    });
  6. Use the Maybe monad for safe value handling

    master

    The Maybe<A> type is a monad used to represent an optional value that might be present (Just) or absent (Nothing). It provides a functional interface to transform and chain operations without manual null or undefined checks.

    Core methods include:

    • map<B>(cb: (arg: A) => B): Transforms the value inside the Maybe if it exists.
    • chain<B>(cb: (arg: A) => Maybe<B>): Chains operations that themselves return a Maybe (flatmap).
    • alt<B>(value: B): Provides a fallback value if the current Maybe is Nothing.
    • cata<B, C>(obj: { Just: (arg: A) => B; Nothing: () => C }): Performs catamorphism (folding) by applying one of two functions based on the state.
    • isJust() / isNothing(): Predicates to check the state.
    • value(): Retrieves the underlying value or undefined if Nothing.
    import { Just, Nothing, nullable } from './maybe'
    
    // Example of chaining operations safely
    const result = nullable("hello")
      .map(s => s.toUpperCase())
      .chain(s => s.length > 0 ? Just(s) : Nothing)
    
    console.log(result.value()) // "HELLO"
  7. Safely get array elements (head, last, tail)

    master

    These utilities provide safe access to array boundaries, returning a Maybe to handle empty arrays:

    • head(array): Returns Just(firstElement) or Nothing if empty.
    • last(array): Returns Just(lastElement) or Nothing if empty.
    • tail(array): Returns Just(remainingElements) (everything except the first) or Nothing if empty.
    import { head, last, tail } from "pratica"
    
    head([5, 1, 2]) // Just(5)
    head([])        // Nothing
    
    last([5, 1, 2]) // Just(2)
    
    tail([5, 1, 2]) // Just([1, 2])
  8. Transform and extract data from a Maybe

    master

    Use the following methods to manipulate the value inside a Maybe:

    • Maybe.map(fn): Runs fn on the data if it is a Just. If Nothing, it skips the function.
    • Maybe.chain(fn): Used when fn returns another Maybe. This prevents nesting (e.g., Maybe<Maybe<T>>).
    • Maybe.alt(defaultValue): Returns a Just containing the defaultValue if the current Maybe is Nothing.
    • Maybe.ap(maybeFunc): Applies a function wrapped in a Maybe to the value inside the current Maybe.
    • Maybe.value(): Returns the raw value if Just, or undefined if Nothing.
    • Maybe.isJust() / Maybe.isNothing(): Returns a boolean indicating the type.
    • Maybe.inspect(): Returns a string representation (e.g., Just(86) or Nothing) for debugging.
    import { nullable, Just } from "pratica"
    
    // Using chain to handle nested nullables
    nullable({ height: 180 })
      .chain((p) => nullable(p.height))
      .map((h) => h * 2.2)
      .cata({
        Just: (h) => console.log(h),
        Nothing: () => console.log("No height found"),
      })
    
    // Using ap to apply functions
    Just((x) => (y) => x + y)
      .ap(Just(6))
      .ap(Just(7))
      .cata({
        Just: (result) => console.log(result), // 13
        Nothing: () => console.log("Error"),
      })
  9. Safely find items or collect monads

    master

    Utilities for searching and aggregating monads:

    • tryFind(predicate)(array): Returns a Maybe containing the first item that satisfies the predicate.
    • collectResult(array): Takes an array of Result objects. Returns Ok(values) if all are Ok, otherwise returns the first Err encountered.
    • collectMaybe(array): Takes an array of Maybe objects. Returns Just(values) if all are Just, otherwise returns Nothing.
    import { tryFind, collectResult, collectMaybe, Ok, Err, Just, Nothing } from "pratica"
    
    // tryFind
    tryFind((u) => u.id === "123")([{ id: "123" }]) // Just({ id: "123" })
    
    // collectResult
    collectResult([Ok(1), Ok(2)]) // Ok([1, 2])
    collectResult([Ok(1), Err("fail")]) // Err("fail")
    
    // collectMaybe
    collectMaybe([Just(1), Just(2)]) // Just([1, 2])
    collectMaybe([Just(1), Nothing]) // Nothing
  10. Safely parse dates with parseDate

    master

    The parseDate utility safely attempts to parse a date string. It returns a Maybe monad:

    • Just(Date): If the string is a valid date.
    • Nothing: If the string is invalid or null.

    Because it returns a Maybe, you can immediately chain it with .alt(), .map(), or .chain().

    import { parseDate } from "pratica"
    
    parseDate("2019-02-13T21:04:10.984Z")
      .cata({
        Just: (date) => console.log(date.toISOString()),
        Nothing: () => console.log("Invalid date"),
      })
  11. Safely access nested properties with get

    master

    The get utility allows you to safely retrieve a value from a deeply nested object using a path array. It returns a Maybe.

    If any part of the path is missing or invalid, it returns Nothing instead of throwing a TypeError.

    import { get } from "pratica"
    
    const data = { children: [{ name: "bob" }, { children: [{ name: "lera" }] }] }
    
    get(["children", 1, "children", 0, "name"])(data).cata({
      Just: (name) => console.log(name), // "lera"
      Nothing: () => console.log("Not found"),
    })
  12. Convert Maybe to Result

    master

    You can convert a Maybe into a Result using .toResult().

    • A Just(value) becomes Ok(value).
    • A Nothing becomes Err() (with no value passed).

    When using .cata() on a Result, you must provide Ok and Err handlers instead of Just and Nothing.

    import { Just, Nothing } from "pratica"
    
    Just(8)
      .toResult()
      .cata({
        Ok: (n) => console.log(n), // 8
        Err: () => console.log(`No value`),
      })