Option & Result
If you’ve written a function that can fail and thought “should I throw? return null? return an object with a success flag?”, these two types sort that out.
Option is for values that might not exist. Result is for operations that can fail with an error you care about. Both force you to handle the unhappy path before you can use the value. The compiler won’t let you forget.
They use numeric _tag (0 for None/Err, 1 for Some/Ok) instead of strings. V8 can branch on numbers in a single CPU instruction.
Option
Section titled “Option”type None = { readonly _tag: 0 }type Some<A> = { readonly _tag: 1; readonly value: A }type Option<A> = None | Some<A>Wraps a value that might not be there. none is a singleton.
Constructors
Section titled “Constructors”some<A>(value: A): Option<A>none: NonefromNullable<A>(value: A | null | undefined): Option<NonNullable<A>>fromPredicate<A>(pred: (a: A) => boolean): (a: A) => Option<A>Transforms
Section titled “Transforms”map<A, B>(f: (a: A) => B): (o: Option<A>) => Option<B>flatMap<A, B>(f: (a: A) => Option<B>): (o: Option<A>) => Option<B>filter<A>(pred: (a: A) => boolean): (o: Option<A>) => Option<A>tap<A>(f: (a: A) => void): (o: Option<A>) => Option<A>Extraction
Section titled “Extraction”getOrElse<B>(onNone: () => B): <A>(o: Option<A>) => A | BgetWithDefault<B>(value: B): <A>(o: Option<A>) => A | Bmatch<B, A, C = B>(onNone: () => B, onSome: (a: A) => C): (o: Option<A>) => B | CtoNullable<A>(o: Option<A>): A | nulltoUndefined<A>(o: Option<A>): A | undefinedtoResult<E>(defaultError: E): <A>(o: Option<A>) => Result<A, E>Guards
Section titled “Guards”isSome<A>(o: Option<A>): o is Some<A>isNone<A>(o: Option<A>): o is Noneimport { pipe, O } from '@stopcock/fp'
pipe( O.fromNullable(config.port), O.map(p => parseInt(p, 10)), O.filter(p => p > 0 && p < 65536), O.getOrElse(() => 3000),)
pipe( O.fromNullable(address), O.flatMap(a => O.fromNullable(a.zip)), O.match(() => 'no zip', zip => zip),)Result
Section titled “Result”type Err<E> = { readonly _tag: 0; readonly error: E }type Ok<A> = { readonly _tag: 1; readonly value: A }type Result<A, E> = Ok<A> | Err<E>Constructors
Section titled “Constructors”ok<A>(value: A): Result<A, never>err<E>(error: E): Result<never, E>tryCatch<A>(thunk: () => A): Result<A, unknown>fromNullable<E>(defaultError: E): <A>(value: A | null | undefined) => Result<NonNullable<A>, E>tryCatch wraps a function that might throw. The error type comes back as unknown because JS can throw anything, so you’ll need to narrow it yourself.
Transforms
Section titled “Transforms”map<A, B>(f: (a: A) => B): <E>(r: Result<A, E>) => Result<B, E>mapErr<E, F>(f: (e: E) => F): <A>(r: Result<A, E>) => Result<A, F>flatMap<A, B, E2>(f: (a: A) => Result<B, E2>): <E>(r: Result<A, E>) => Result<B, E | E2>tap<A>(f: (a: A) => void): <E>(r: Result<A, E>) => Result<A, E>tapErr<E>(f: (e: E) => void): <A>(r: Result<A, E>) => Result<A, E>Extraction
Section titled “Extraction”getOrElse<B>(onErr: () => B): <A, E>(r: Result<A, E>) => A | Bmatch<E, B, A, C = B>(onErr: (e: E) => B, onOk: (a: A) => C): (r: Result<A, E>) => B | CtoOption<A, E>(r: Result<A, E>): Option<A>Guards
Section titled “Guards”isOk<A, E>(r: Result<A, E>): r is Ok<A>isErr<A, E>(r: Result<A, E>): r is Err<E>import { pipe, R } from '@stopcock/fp'
const parsed = R.tryCatch(() => JSON.parse(input))
pipe( parsed, R.map((obj: { value: number }) => obj.value), R.flatMap(v => v > 0 ? R.ok(v) : R.err('must be positive')), R.match( err => console.error(err), val => console.log(val), ),)Converting
Section titled “Converting”// Option → Resultpipe(O.fromNullable(x), O.toResult('was null'))
// Result → Option (discards error)pipe(R.ok('hello'), R.toOption) // Some('hello')pipe(R.err('boom'), R.toOption) // None