pratica
repository·master·Indexed 19 days ago
https://github.com/rametta/praticaA 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).
What's inside pratica
- 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.
How the Maybe monad works
masterThe
Maybemonad is used to handle nullable or unreliable data safely, preventing runtime errors caused bynullorundefined. AMaybecan be one of two types:Just: Contains the available data.Nothing: Represents missing data.
Most operations on a
Maybewill "short-circuit": if the type isNothing, 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 })How the Result monad works
masterThe
Resultmonad is used for handling conditional logic and error states, serving as a functional alternative toif/elseortry/catchblocks. AResultis either:Ok: Contains the successful value.Err: Contains error information (e.g., a message or error object).
Methods like
.chain()will stop execution and immediately trigger theErrhandler if any step in the chain returns anErr.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 })Install Pratica via npm, yarn, or bun
masterYou 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 praticaUse the Result monad for error handling
masterThe
Result<O, E>monad is used to represent the outcome of an operation that can either succeed with a value of typeO(Ok) or fail with an error of typeE(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 usingcb. If the result is an error, it remains unchanged.mapErr(cb): Transforms the error value usingcb. If the result is a success, it remains unchanged.chain(cb): Chains a new operation that returns aResult. Used for sequential operations where the next step depends on the success of the previous one.chainErr(cb): Chains an operation that returns aResultspecifically for the error case.bimap(ok, err): Transforms both the success and error values in a single pass.cata(obj): A catamorphism that collapses theResultinto a single value by providing handlers for bothOkandErrcases.toMaybe(): Converts theResultinto aMaybetype.isOk()/isErr(): Predicates to check the state of the result.value(): Returns the underlying value (eitherOorE).
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}` });Use the Maybe monad for safe value handling
masterThe
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 theMaybeif it exists.chain<B>(cb: (arg: A) => Maybe<B>): Chains operations that themselves return aMaybe(flatmap).alt<B>(value: B): Provides a fallback value if the currentMaybeisNothing.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 orundefinedifNothing.
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"Safely get array elements (head, last, tail)
masterThese utilities provide safe access to array boundaries, returning a
Maybeto handle empty arrays:head(array): ReturnsJust(firstElement)orNothingif empty.last(array): ReturnsJust(lastElement)orNothingif empty.tail(array): ReturnsJust(remainingElements)(everything except the first) orNothingif 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])Transform and extract data from a Maybe
masterUse the following methods to manipulate the value inside a
Maybe:Maybe.map(fn): Runsfnon the data if it is aJust. IfNothing, it skips the function.Maybe.chain(fn): Used whenfnreturns anotherMaybe. This prevents nesting (e.g.,Maybe<Maybe<T>>).Maybe.alt(defaultValue): Returns aJustcontaining thedefaultValueif the currentMaybeisNothing.Maybe.ap(maybeFunc): Applies a function wrapped in aMaybeto the value inside the currentMaybe.Maybe.value(): Returns the raw value ifJust, orundefinedifNothing.Maybe.isJust()/Maybe.isNothing(): Returns a boolean indicating the type.Maybe.inspect(): Returns a string representation (e.g.,Just(86)orNothing) 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"), })Safely find items or collect monads
masterUtilities for searching and aggregating monads:
tryFind(predicate)(array): Returns aMaybecontaining the first item that satisfies thepredicate.collectResult(array): Takes an array ofResultobjects. ReturnsOk(values)if all areOk, otherwise returns the firstErrencountered.collectMaybe(array): Takes an array ofMaybeobjects. ReturnsJust(values)if all areJust, otherwise returnsNothing.
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]) // NothingSafely parse dates with parseDate
masterThe
parseDateutility safely attempts to parse a date string. It returns aMaybemonad: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"), })Safely access nested properties with get
masterThe
getutility allows you to safely retrieve a value from a deeply nested object using a path array. It returns aMaybe.If any part of the path is missing or invalid, it returns
Nothinginstead of throwing aTypeError.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"), })Convert Maybe to Result
masterYou can convert a
Maybeinto aResultusing.toResult().- A
Just(value)becomesOk(value). - A
NothingbecomesErr()(with no value passed).
When using
.cata()on aResult, you must provideOkandErrhandlers instead ofJustandNothing.import { Just, Nothing } from "pratica" Just(8) .toResult() .cata({ Ok: (n) => console.log(n), // 8 Err: () => console.log(`No value`), })- A