typescript-monads helps you write safer code by using abstractions over messy control flow and state.
You can use this library in the browser, node, or a bundler
npm install typescript-monads
<head>
<script src="https://unpkg.com/typescript-monads"></script>
<!-- or use a specific version to avoid a http redirect -->
<script src="https://unpkg.com/typescript-monads@5.3.0/index.min.js"></script>
</head>
var someRemoteValue;
typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
The Maybe
monad represents values that may or may not exist. It's a safe way to handle potentially null or undefined values without resorting to null checks throughout your code.
import { maybe, none } from 'typescript-monads'
// Creating Maybe instances
const someValue = maybe(42) // Maybe with a value
const noValue = maybe(null) // Maybe with no value (None)
const alsoNoValue = none<number>() // Explicitly create a None
// Safe value access
someValue.valueOr(0) // 42
noValue.valueOr(0) // 0
someValue.valueOrCompute(() => expensiveCalculation()) // 42 (computation skipped)
noValue.valueOrCompute(() => expensiveCalculation()) // result of computation
// Conditional execution with pattern matching
someValue.match({
some: val => console.log(`Got a value: ${val}`),
none: () => console.log('No value present')
}) // logs: "Got a value: 42"
// Side effects with tap
someValue.tap({
some: val => console.log(`Got ${val}`),
none: () => console.log('Nothing to see')
})
// Conditional side effects
someValue.tapSome(val => console.log(`Got ${val}`))
noValue.tapNone(() => console.log('Nothing here'))
// Chaining operations (only executed for Some values)
maybe(5)
.map(n => n * 2) // maybe(10)
.filter(n => n > 5) // maybe(10)
.flatMap(n => maybe(n + 1)) // maybe(11)
// Transforming to other types
maybe(5).toResult('No value found') // Ok(5)
maybe(null).toResult('No value found') // Fail('No value found')
// Working with RxJS (with rxjs optional dependency)
import { maybeToObservable } from 'typescript-monads'
maybeToObservable(maybe(5)) // Observable that emits 5 and completes
maybeToObservable(none()) // Observable that completes without emitting
The List
monad is a lazily evaluated collection with chainable operations. It provides many of the common list processing operations found in functional programming languages.
import { List } from 'typescript-monads'
// Creating Lists
const fromValues = List.of(1, 2, 3, 4, 5)
const fromIterable = List.from([1, 2, 3, 4, 5])
const numbersFromRange = List.range(1, 10) // 1 to 10
const infiniteNumbers = List.integers() // All integers (use with take!)
const empty = List.empty<number>()
// Basic operations
fromValues.toArray() // [1, 2, 3, 4, 5]
fromValues.headOrUndefined() // 1
fromValues.headOr(0) // 1
empty.headOr(0) // 0
// Transformations
fromValues
.map(n => n * 2) // [2, 4, 6, 8, 10]
.filter(n => n > 5) // [6, 8, 10]
.take(2) // [6, 8]
.drop(1) // [8]
// LINQ-style operations
fromValues.sum() // 15
fromValues.all(n => n > 0) // true
fromValues.any(n => n % 2 === 0) // true
fromValues.where(n => n % 2 === 0) // [2, 4]
// Conversion
fromValues.toDictionary() // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }
const users = List.of(
{ id: 'a', name: 'Alice' },
{ id: 'b', name: 'Bob' }
)
users.toDictionary('id') // { 'a': { id: 'a', name: 'Alice' }, 'b': { id: 'b', name: 'Bob' } }
The Either
monad represents values that can be one of two possible types. It's often used to represent a value that can be either a success (Right) or a failure (Left).
import { either } from 'typescript-monads'
// Creating Either instances
const rightValue = either<string, number>(undefined, 42) // Right value
const leftValue = either<string, number>('error', undefined) // Left value
// Checking which side is present
rightValue.isRight() // true
rightValue.isLeft() // false
leftValue.isRight() // false
leftValue.isLeft() // true
// Pattern matching
rightValue.match({
right: val => `Success: ${val}`,
left: err => `Error: ${err}`
}) // "Success: 42"
// Side effects with tap
rightValue.tap({
right: val => console.log(`Got right: ${val}`),
left: err => console.log(`Got left: ${err}`)
})
// Transformation (only applies to Right values)
rightValue.map(n => n * 2) // Either with Right(84)
leftValue.map(n => n * 2) // Either with Left('error') unchanged
// Chaining (flatMap only applies to Right values)
rightValue.flatMap(n => either(undefined, n + 10)) // Either with Right(52)
leftValue.flatMap(n => either(undefined, n + 10)) // Either with Left('error') unchanged
The Reader
monad represents a computation that depends on some external configuration or environment. It's useful for dependency injection.
import { reader } from 'typescript-monads'
// Define a configuration type
interface Config {
apiUrl: string
apiKey: string
}
// Create readers that depend on this configuration
const getApiUrl = reader<Config, string>(config => config.apiUrl)
const getApiKey = reader<Config, string>(config => config.apiKey)
// Compose readers to build more complex operations
const getAuthHeader = getApiKey.map(key => `Bearer ${key}`)
// Create a reader for making an API request
const fetchData = reader<Config, Promise<Response>>(config => {
return fetch(`${config.apiUrl}/data`, {
headers: {
'Authorization': `Bearer ${config.apiKey}`
}
})
})
// Execute the reader with a specific configuration
const config: Config = {
apiUrl: 'https://api.example.com',
apiKey: 'secret-key-123'
}
const apiUrl = getApiUrl.run(config) // 'https://api.example.com'
const authHeader = getAuthHeader.run(config) // 'Bearer secret-key-123'
fetchData.run(config).then(response => {
// Handle API response
})
The Result
monad represents operations that can either succeed with a value or fail with an error. It's similar to Either but with more specific semantics for success/failure.
import { ok, fail, catchResult } from 'typescript-monads'
// Creating Result instances
const success = ok<number, string>(42) // Success with value 42
const failure = fail<number, string>('error') // Failure with error 'error'
// Safely catching exceptions
const result = catchResult<number, Error>(
() => {
// Code that might throw
if (Math.random() > 0.5) {
throw new Error('Failed')
}
return 42
}
)
// Checking result type
success.isOk() // true
success.isFail() // false
failure.isOk() // false
failure.isFail() // true
// Extracting values safely
success.unwrapOr(0) // 42
failure.unwrapOr(0) // 0
success.unwrap() // 42
// failure.unwrap() // Throws error
// Convert to Maybe
success.maybeOk() // Maybe with Some(42)
failure.maybeOk() // Maybe with None
success.maybeFail() // Maybe with None
failure.maybeFail() // Maybe with Some('error')
// Pattern matching
success.match({
ok: val => `Success: ${val}`,
fail: err => `Error: ${err}`
}) // "Success: 42"
// Transformations
success.map(n => n * 2) // Ok(84)
failure.map(n => n * 2) // Fail('error') unchanged
failure.mapFail(e => `${e}!`) // Fail('error!')
// Chaining
success.flatMap(n => ok(n + 10)) // Ok(52)
failure.flatMap(n => ok(n + 10)) // Fail('error') unchanged
// Side effects
success.tap({
ok: val => console.log(`Success: ${val}`),
fail: err => console.log(`Error: ${err}`)
})
success.tapOk(val => console.log(`Success: ${val}`))
failure.tapFail(err => console.log(`Error: ${err}`))
// Chaining with side effects
success
.tapOkThru(val => console.log(`Success: ${val}`))
.map(n => n * 2)
// Converting to promises
import { resultToPromise } from 'typescript-monads'
resultToPromise(success) // Promise that resolves to 42
resultToPromise(failure) // Promise that rejects with 'error'
The State
monad represents computations that can read and transform state. It's useful for threading state through a series of operations.
import { state } from 'typescript-monads'
// Define a state type
interface AppState {
count: number
name: string
}
// Initial state
const initialState: AppState = {
count: 0,
name: ''
}
// Create operations that work with the state
const incrementCount = state<AppState, number>(s =>
[{ ...s, count: s.count + 1 }, s.count + 1]
)
const setName = (name: string) => state<AppState, void>(s =>
[{ ...s, name }, undefined]
)
const getCount = state<AppState, number>(s => [s, s.count])
// Compose operations
const operation = incrementCount
.flatMap(() => setName('Alice'))
.flatMap(() => getCount)
// Run the state operation
const result = operation.run(initialState)
console.log(result.state) // { count: 1, name: 'Alice' }
console.log(result.value) // 1
The Logger
monad lets you collect logs during a computation. It's useful for debugging or creating audit trails.
import { logger, tell } from 'typescript-monads'
// Create a logger with initial logs and value
const initialLogger = logger<string, number>(['Starting process'], 0)
// Add logs and transform value
const result = initialLogger
.flatMap(val => {
return logger(['Incrementing value'], val + 1)
})
.flatMap(val => {
return logger(['Doubling value'], val * 2)
})
// Extract all logs and final value
result.runUsing(({ logs, value }) => {
console.log('Logs:', logs) // ['Starting process', 'Incrementing value', 'Doubling value']
console.log('Final value:', value) // 2
})
// Start with a single log entry
const startLogger = tell<string>('Beginning')
// Add a value to the logger
const withValue = logger.startWith<string, number>('Starting with value:', 42)
// Extract results using pattern matching
const output = result.runUsing(({ logs, value }) => {
return {
history: logs.join('\n'),
result: value
}
})
This library offers RxJS integration with the Maybe
and Result
monads:
import { maybeToObservable } from 'typescript-monads'
import { resultToObservable } from 'typescript-monads'
import { of } from 'rxjs'
import { flatMap } from 'rxjs/operators'
// Convert Maybe to Observable
of(maybe(5)).pipe(
flatMap(maybeToObservable)
).subscribe(val => console.log(val)) // logs 5 and completes
// Convert Result to Observable
of(ok(42)).pipe(
flatMap(resultToObservable)
).subscribe(
val => console.log(`Success: ${val}`),
err => console.error(`Error: ${err}`)
)
You can convert Result
monads to promises:
import { resultToPromise } from 'typescript-monads'
// Convert Result to Promise
resultToPromise(ok(42))
.then(val => console.log(`Success: ${val}`))
.catch(err => console.error(`Error: ${err}`))
// Catch exceptions and convert to Result
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data')
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`)
}
return ok(await response.json())
} catch (error) {
return fail(error)
}
}
Contributions are welcome! Please feel free to submit a Pull Request.
MIT