fp-ts option and either

fp-ts Option and Either Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "fp-ts option and either" with this command: npx skills add whatiskadudoing/fp-ts-skills/whatiskadudoing-fp-ts-skills-fp-ts-option-and-either

fp-ts Option and Either Guide

This skill covers practical usage of Option and Either from fp-ts for safer TypeScript code.

When to Use Option vs Either

Use Option when:

  • A value may or may not exist (nullable/undefined scenarios)

  • You don't need to know WHY a value is missing

  • Working with optional fields, array lookups, or dictionary access

Use Either when:

  • An operation can fail and you need error information

  • Replacing try-catch blocks

  • You need to communicate different failure reasons

  • Building validation pipelines

Imports

// Option imports import * as O from 'fp-ts/Option' import { pipe } from 'fp-ts/function'

// Either imports import * as E from 'fp-ts/Either' import { pipe } from 'fp-ts/function'

// Both together import * as O from 'fp-ts/Option' import * as E from 'fp-ts/Either' import { pipe, flow } from 'fp-ts/function'

Option: Handling Nullable Values

Converting Nullable Values to Option

import * as O from 'fp-ts/Option' import { pipe } from 'fp-ts/function'

// fromNullable: converts null/undefined to None, otherwise Some const maybeUser = O.fromNullable(getUserById(id)) // Option<User>

// fromPredicate: creates Some if predicate passes, None otherwise const positiveNumber = O.fromPredicate((n: number) => n > 0)(value)

// Manual construction const some = O.some(42) // Some(42) const none = O.none // None

Extracting Values from Option

// getOrElse: provide a default value const username = pipe( maybeUser, O.map(user => user.name), O.getOrElse(() => 'Anonymous') )

// getOrElseW: wider type for default (when types differ) const result = pipe( maybeNumber, O.getOrElseW(() => 'not found' as const) ) // number | 'not found'

// fold/match: handle both cases explicitly const greeting = pipe( maybeUser, O.fold( () => 'Hello, stranger!', // None case (user) => Hello, ${user.name}! // Some case ) )

// Alternative: match (same as fold, more descriptive name) const greeting = pipe( maybeUser, O.match( () => 'Hello, stranger!', (user) => Hello, ${user.name}! ) )

Transforming Option Values

// map: transform the inner value (if present) const userName = pipe( maybeUser, O.map(user => user.name) ) // Option<string>

// chain (flatMap): when transformation returns Option const userEmail = pipe( maybeUser, O.chain(user => O.fromNullable(user.email)) ) // Option<string>

// filter: keep Some only if predicate passes const adultUser = pipe( maybeUser, O.filter(user => user.age >= 18) )

Combining Options

// sequenceArray: convert Option<T>[] to Option<T[]> import { sequenceArray } from 'fp-ts/Option'

const maybeNumbers: O.Option<number>[] = [O.some(1), O.some(2), O.some(3)] const allNumbers = sequenceArray(maybeNumbers) // Some([1, 2, 3])

const withNone: O.Option<number>[] = [O.some(1), O.none, O.some(3)] const result = sequenceArray(withNone) // None

// ap: apply Option<(a: A) => B> to Option<A> import { ap } from 'fp-ts/Option'

const add = (a: number) => (b: number) => a + b const result = pipe( O.some(add), ap(O.some(1)), ap(O.some(2)) ) // Some(3)

Either: Handling Errors

Converting Try-Catch to Either

import * as E from 'fp-ts/Either' import { pipe } from 'fp-ts/function'

// tryCatch: wraps throwing functions const parseJSON = (json: string): E.Either<Error, unknown> => E.tryCatch( () => JSON.parse(json), (error) => error instanceof Error ? error : new Error(String(error)) )

// Usage const result = parseJSON('{"valid": "json"}') // Right({ valid: 'json' }) const error = parseJSON('invalid json') // Left(SyntaxError)

// tryCatchK: creates a function that returns Either const safeParseJSON = E.tryCatchK( JSON.parse, (error) => error instanceof Error ? error : new Error(String(error)) )

Creating Either Values

// Manual construction const success = E.right(42) // Right(42) const failure = E.left('Something went wrong') // Left('Something went wrong')

// fromNullable: convert nullable to Either with error const getUser = (id: string): E.Either<string, User> => pipe( findUserById(id), E.fromNullable(User not found: ${id}) )

// fromPredicate: create Right if predicate passes const validateAge = E.fromPredicate( (age: number) => age >= 18, (age) => Age ${age} is below minimum of 18 )

Extracting Values from Either

// fold/match: handle both cases const message = pipe( result, E.fold( (error) => Error: ${error.message}, // Left case (data) => Success: ${JSON.stringify(data)} // Right case ) )

// getOrElse: provide default for Left case const value = pipe( result, E.getOrElse((error) => defaultValue) )

// getOrElseW: wider type for default const value = pipe( result, E.getOrElseW((error) => null) ) // T | null

Transforming Either Values

// map: transform Right value const userAge = pipe( getUser(id), E.map(user => user.age) ) // Either<string, number>

// mapLeft: transform Left value (error mapping) const withBetterError = pipe( result, E.mapLeft(error => new CustomError(error.message)) )

// bimap: transform both sides const formatted = pipe( result, E.bimap( (error) => Error: ${error}, (value) => Value: ${value} ) )

// chain (flatMap): when transformation returns Either const userProfile = pipe( getUser(id), E.chain(user => getProfile(user.profileId)) ) // Either<string, Profile>

// chainW: chain with wider error type const result = pipe( validateEmail(input), // Either<ValidationError, string> E.chainW(sendEmail) // Either<NetworkError, Response> ) // Either<ValidationError | NetworkError, Response>

Validation Pattern

import * as E from 'fp-ts/Either' import { pipe } from 'fp-ts/function'

type ValidationError = { field: string; message: string }

const validateEmail = (email: string): E.Either<ValidationError, string> => email.includes('@') ? E.right(email) : E.left({ field: 'email', message: 'Invalid email format' })

const validatePassword = (password: string): E.Either<ValidationError, string> => password.length >= 8 ? E.right(password) : E.left({ field: 'password', message: 'Password too short' })

// Sequential validation (stops at first error) const validateUser = (email: string, password: string) => pipe( E.Do, E.bind('email', () => validateEmail(email)), E.bind('password', () => validatePassword(password)) )

// For collecting all errors, use Validation from fp-ts import * as A from 'fp-ts/Apply' import { getSemigroup } from 'fp-ts/NonEmptyArray'

const applicativeValidation = E.getApplicativeValidation( getSemigroup<ValidationError>() )

Common Patterns

Safe Array Access

import * as A from 'fp-ts/Array' import * as O from 'fp-ts/Option'

// head: safely get first element const first = A.head([1, 2, 3]) // Some(1) const empty = A.head([]) // None

// lookup: safely get element by index const second = A.lookup(1)([1, 2, 3]) // Some(2) const outOfBounds = A.lookup(10)([1, 2, 3]) // None

Safe Object Property Access

import * as R from 'fp-ts/Record' import * as O from 'fp-ts/Option'

const config: Record<string, string> = { host: 'localhost' }

const host = R.lookup('host')(config) // Some('localhost') const missing = R.lookup('port')(config) // None

Converting Between Option and Either

import * as O from 'fp-ts/Option' import * as E from 'fp-ts/Either'

// Option to Either const toEither = O.toEither(() => 'Value was missing') const either = pipe(maybeValue, toEither) // Either<string, T>

// Either to Option (discards error) const toOption = E.toOption const option = pipe(either, toOption) // Option<T>

Async Operations with TaskEither

import * as TE from 'fp-ts/TaskEither' import { pipe } from 'fp-ts/function'

// Wrap async operations const fetchUser = (id: string): TE.TaskEither<Error, User> => TE.tryCatch( () => fetch(/api/users/${id}).then(r => r.json()), (error) => error instanceof Error ? error : new Error(String(error)) )

// Chain async operations const getUserProfile = (id: string) => pipe( fetchUser(id), TE.chain(user => fetchProfile(user.profileId)), TE.map(profile => profile.displayName) )

// Execute const result = await getUserProfile('123')() // Either<Error, string>

Best Practices

Prefer pipe over method chaining for better composition and tree-shaking

Use fromNullable at system boundaries to convert external nullable values

Use descriptive error types with Either instead of generic strings

Leverage type inference - avoid explicit type annotations when TypeScript can infer

Use chainW when error types differ to automatically widen the union

Prefer fold /match for final extraction to ensure both cases are handled

Anti-Patterns to Avoid

Don't Use isSome/isRight for Control Flow

// BAD: loses type narrowing benefits if (O.isSome(maybeUser)) { console.log(maybeUser.value.name) }

// GOOD: use fold/match pipe( maybeUser, O.fold( () => console.log('No user'), (user) => console.log(user.name) ) )

Don't Nest Options/Eithers

// BAD: creates Option<Option<T>> const nested = pipe( maybeUser, O.map(user => O.fromNullable(user.email)) ) // Option<Option<string>>

// GOOD: use chain to flatten const flat = pipe( maybeUser, O.chain(user => O.fromNullable(user.email)) ) // Option<string>

Don't Use getOrElse Too Early

// BAD: extracts value too early, loses composition const name = pipe(maybeUser, O.getOrElse(() => defaultUser)).name

// GOOD: keep in Option context, extract at the end const name = pipe( maybeUser, O.map(user => user.name), O.getOrElse(() => 'Unknown') )

Don't Ignore Left Values

// BAD: silently discards error information const value = pipe(result, E.getOrElse(() => defaultValue))

// GOOD: handle or log errors appropriately const value = pipe( result, E.fold( (error) => { logger.error('Operation failed', error) return defaultValue }, (value) => value ) )

Don't Mix Paradigms

// BAD: mixing try-catch with Either try { const result = pipe( parseJSON(input), E.chain(validate) ) } catch (e) { // This defeats the purpose }

// GOOD: stay in Either world pipe( parseJSON(input), E.chain(validate), E.fold( (error) => handleError(error), (value) => handleSuccess(value) ) )

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

fp-ts-backend

No summary provided by upstream source.

Repository SourceNeeds Review
General

fp-immutable

No summary provided by upstream source.

Repository SourceNeeds Review
General

fp-ts pipe and flow composition

No summary provided by upstream source.

Repository SourceNeeds Review
General

managing side effects functionally

No summary provided by upstream source.

Repository SourceNeeds Review