Berwald is a functional programming library for TypeScript. I built it for fun and to learn more about functional programming.
# Using npm
npm install @tigerabrodioss/berwald
# Using yarn
yarn add @tigerabrodioss/berwald
# Using pnpm
pnpm add @tigerabrodioss/berwald
Berwald provides a comprehensive set of tools for functional programming in TypeScript:
- Core data types: Option, Either, List, Tuple
- Pattern matching: Expressive pattern matching syntax
- Higher-order functions: Utilities for working with functions
- Type classes: Semigroup, Monoid, Group
- Utilities: Pipe, Compose, Curry
All modules are exported as namespaces to prevent name collisions.
import { Option } from '@tigerabrodioss/berwald'
// Create options
const greeting = Option.some('Hello')
const empty = Option.none
// Transform values safely
const uppercased = Option.map((s: string) => s.toUpperCase())(greeting) // Some('HELLO')
// Handle missing values with a default
const value = Option.getOrElse('Default')(uppercased) // 'HELLO'
const fallback = Option.getOrElse('Default')(empty) // 'Default'
import { Either, TryCatch } from '@tigerabrodioss/berwald'
// Parse JSON safely
const parseJSON = TryCatch.tryCatch(
JSON.parse,
(error: unknown) => `Invalid JSON: ${String(error)}`
)
const result = parseJSON('{"name":"Alice"}') // Right({ name: 'Alice' })
const error = parseJSON('invalid') // Left('Invalid JSON: ...')
// Process result
if (Either.isRight(result)) {
console.log(result.right.name) // 'Alice'
} else {
console.error(result.left) // Error message
}
import { List } from '@tigerabrodioss/berwald'
// Create a list
const numbers = List.fromArray([1, 2, 3, 4, 5])
// Transform list contents
const doubled = List.map((n: number) => n * 2)(numbers)
const evens = List.filter((n: number) => n % 2 === 0)(numbers)
// Convert back to array
const result = List.toArray(doubled) // [2, 4, 6, 8, 10]
import { Match } from '@tigerabrodioss/berwald'
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number }
| { type: 'triangle'; base: number; height: number }
const calculateArea = (shape: Shape) =>
Match.match(shape)(
Match.when(
(s) => s.type === 'circle',
(s) => Math.PI * (s as { radius: number }).radius ** 2
),
Match.when(
(s) => s.type === 'rectangle',
(s) =>
(s as { width: number; height: number }).width *
(s as { width: number; height: number }).height
),
Match.when(
(s) => s.type === 'triangle',
(s) =>
((s as { base: number; height: number }).base *
(s as { base: number; height: number }).height) /
2
)
)
import { Pipe, Compose } from '@tigerabrodioss/berwald'
const add = (a: number) => (b: number) => a + b
const multiply = (a: number) => (b: number) => a * b
const toString = (a: number) => `Result: ${a}`
// Pipe: left-to-right composition
const calculate1 = Pipe.pipe(
5,
add(10), // 15
multiply(2), // 30
toString // "Result: 30"
)
// Compose: right-to-left composition
const processNumber = Compose.compose(toString, multiply(2), add(10))
const calculate2 = processNumber(5) // "Result: 30"
Represents values that may or may not exist.
some<A>(value: A): Option<A>
- Creates a Some containing a valuenone: Option<never>
- Represents no valueisSome<A>(option: Option<A>): option is Some<A>
- Type guard for SomeisNone<A>(option: Option<A>): option is None
- Type guard for Nonemap<A, B>(f: (a: A) => B): (option: Option<A>) => Option<B>
- Maps the contained valueflatMap<A, B>(f: (a: A) => Option<B>): (option: Option<A>) => Option<B>
- Maps and flattensgetOrElse<A>(defaultValue: A): (option: Option<A>) => A
- Gets value or returns default
Represents a value of one of two possible types (a disjoint union).
right<E, A>(value: A): Either<E, A>
- Creates a Right (success) valueleft<E, A>(value: E): Either<E, A>
- Creates a Left (error) valueisRight<E, A>(either: Either<E, A>): either is Right<A>
- Type guard for RightisLeft<E, A>(either: Either<E, A>): either is Left<E>
- Type guard for Leftmap<E, A, B>(f: (a: A) => B): (either: Either<E, A>) => Either<E, B>
- Maps Right values
An immutable linked list implementation.
nil: List<never>
- The empty listcons<A>(head: A, tail: List<A>): List<A>
- Creates a new listfromArray<A>(arr: ReadonlyArray<A>): List<A>
- Creates a list from an arraytoArray<A>(list: List<A>): ReadonlyArray<A>
- Converts a list to an arraymap<A, B>(f: (a: A) => B): (list: List<A>) => List<B>
- Maps over list items
match<A>(value: A)
- Starts a pattern match expressionwhen<A, B>(predicate: (a: A) => boolean, transform: (a: A) => B)
- Defines a match patternotherwise<A, B>(transform: (a: A) => B)
- Default case for when no pattern matches
pipe<A, B, ...>(a: A, ab: (a: A) => B, ...): ...
- Pipes a value through functionsflow<A, B, ...>(ab: (a: A) => B, ...): (a: A) => ...
- Creates a pipeline functioncurry<A, B, ...>(f: (a: A, b: B, ...) => R): (a: A) => (b: B) => ... => R
- Curries a function
MIT © Tiger Abrodi