0.3.0 • Published 4 years ago

@dise-international/option v0.3.0

Weekly downloads
76
License
ISC
Repository
-
Last release
4 years ago

Option

Option is a data structure heavily inspired by the Rust Option enum that encodes values that either exist or not. Sometimes called Maybe, it's made up of a tagged union with two variants:

  • Some (x), representing the presence of a value
  • None, representing the absence of a value.

Option is useful in place of values like null or undefined, and carry several benefits:

  • Option offers semantic information to the developer - Option<number> tells us both that it is an Option, and that it should contain a number.
  • It forces us to deal with the absence of a value, while providing ergonomic, expressive ways to do so.
  • None can not be confused with false, 0 or "". Some(0) is vastly different from None.

Similar to @dise-international/result, the Option instance implements only a few basic methods and properties, while more expressive functions are implemented as static methods. This is to keep the actual class instance clean, and to promote piping values through functional transformations.

Installation

@dise-international/option is available in both TypeScript and JavaScript format.

Install via NPM

$ npm install @dise-international/option

Import the module

import { Option } from '@dise-international/option';

Or require it

const { Option } = require('@dise-international/option');

Usage

The guide, and the examples in it, assumes a TypeScript environment. If you're using plain JavaScript, the guide reads exactly the same, but without type declarations and generics.


Imports / Exports

The module exports three members; Option, Some, and None, where the latter two are symbols used to tag an Option as either variant. All of the functionality is implemented as properties, methods, or static functions on Option.

Imports are omitted from the code examples below, unless there is special reason to include them. If none are present in the example, assume that there is an implicit:

import { Option, Some, None } from '@dise-international/option';

Variants

None

A unique symbol representing the absence of a value. Exported alongside Option.

Examples
import { None, Option } from '@dise-international/option';

const option = new Option<number>(None);

assert.isTrue(option.isNone);

Some

A unique symbol representing the presence of a value. Exported alongside Option.

Examples
import { None, Option } from '@dise-international/option';

const option = new Option<number>(Some, 1);

assert.isTrue(option.isSome);

Properties

The properties implemented on the Option instance are listed here.

type: Symbol

A symbol that tags the Option as either Some or None.
Set during construction.

const some = Option.some<number>(1);
assert.equals(some.type, Some);

const none = Option.none<number>(1);
assert.equals(none.type, None);

value: T | never

Contains the Option value.
Trying to access value on a None will result in an error

Examples
const some = Option.some<number>(1);
assert.equals(some.value, 1);

const none = Option.none<number>();
assert.throws(() => none.value, 'Can not read value from None');

isSome: boolean

Returns true it the type is Some.

Examples
const some = Option.some<number>(1);
assert.isTrue(some.isSome);

const none = Option.none<number>();
assert.isFalse(none.isSome);

isNone: boolean

Returns true it the type is None.

Examples
const some = Option.some<number>(1);
assert.isFalse(some.isNone);

const none = Option.none<number>();
assert.isTrue(none.isNone);

Methods

The methods implemented on the Option instance are listed here.

T refers to Option<T>, the dynamic type of the contained value.

map(fn): Option

map<U>(fn: (x: T) => U): Option<U>

Maps this Option<T> to Option<U> by applying a function to the contained value and wrapping it in an Option.

Returns None if this is a None, otherwise returns Some.

Examples
const some = Option.some<number>(1)
                   .map<string>(x => x.toString());

assert.equals(some.value, "1");
const none = Option.none<number>()
                   .map<string>(x => x.toString());

assert.throws(() => none.value, 'Can not read value from None');

chain(fn): Option

chain<U>(fn: (x: T) => Option<U>): Option<U>

Maps this Option<T> to Option<U> by applying a function to the contained value. Commonly referred to as bind or flatMap.

Returns None if this is a None, otherwise returns the result of fn.

Examples

Type declarations may be omitted for brevity.

Conditionally turn a Some into a None

const some = Option.some(1);

const none = some.chain(x => x > 5
  ? Option.some(x)
  : Option.none());

assert.isTrue(none.isNone);

Compose with Option.some to form map()

const map = (option, fn) => Option.some(option.chain(fn));

const some = map(
  Option.some(1)
  x => x.toString())

assert.equals(some.value, "1");

Use with the identity function to form flatten()

const flatten = (option) => option.chain(x => x);
const maybeNumber: Option<Option<number>> = Option.some(Option.some(1))

assert.equals(flatten(maybeNumber).value, 1);

unwrapOr(or: T): T

unwrapOr(or: T): T

Returns the contained value if this is a Some, otherwise returns or.


equals(option): boolean

equals(option: Option<T>): boolean

Returns true if both options are None, or when the contained values are strictly equal. Otherwise returns false.

ab=
NoneNone✔️
NoneSome
Some (a)Some (a)✔️
Some (a)Some (b)

match\(pattern): U

match<U>(pattern: Record<'Some'|'None', (x?: T) => U>): U

Extracts the contained value by matching on the provided pattern and applying the function.

Examples
const some = Option.some<number>(1);

const value = some.match({
  Some: x => x,
  None: () => 0
});

assert.equals(value, 1);
const none = Option.none<number>();

const value = none.match({
  Some: x => x,
  None: () => 0
});

assert.equals(value, 0);

Operators

The functions implemented as static methods on the Option class are listed here.

some(value): Option

some<T>(value: T): Option<T>

Constructs a new Option of type Some. This is the preferred way to create new Some:s


none(): Option

none<T>(): Option<T>

Constructs a new Option of type None. This is the preferred way to create new None:s


of(value): Option

of<T>(value: T): Option<T>

Alias for Option.some.


from(value): Option

from<T>(x: T): Option<T>

Returns None if value is either null or undefined. Otherwise returns a Some containing the value.


fromEmpty(value): Option

fromEmpty<T extends Record<'length', number>>(xs: T): Option<T>

Takes a value with property length: number and returns None if length is zero or less. Otherwise returns a Some of that value.

Examples
const nonEmptyString: Option<string> = Option.fromEmpty("Hello");
assert.isTrue(nonEmptyString.isSome);

const emptyString: Option<string> = Option.fromEmpty("");
assert.isTrue(emptyString.isNone);
const nonEmptyList: Option<string[]> = Option.fromEmpty(["Hello", "World"]);
assert.isTrue(nonEmptyList.isSome);

const emptyList: Option<string[]> = Option.fromEmpty([]);
assert.isTrue(emptyList.isNone);

isSome(option): boolean

isSome(option: Option<any>): boolean

Operator alias of the isSome property. Returns true if the option is a Some, otherwise returns false.

Examples

const some = Option.some<number>(1);

assert.isTrue(Option.isSome(some));

isNone(option): boolean

isNone(option: Option<any>): boolean

Operator alias of the isNone property. Returns true if the option is a None, otherwise returns false.

Examples

const none = Option.none<number>();

assert.isTrue(Option.isNone(none));

map (fn) (option): Option

map<T, U>(fn: (x: T) => U): (option: Option<T>) => Option<U>

Curried operator alias of the map() method. Takes option as it's second parameter.


chain (fn) (option): Option

chain<T, U>(fn: (x: T) => Option<U>): (option: Option<T>) => Option<U>

Curried operator alias of the chain() method. Takes option as it's second parameter.


unwrapOr (or) (option): T

unwrapOr<T>(or: T): (option: Option<T>) => T

Curried operator alias of the unwrapOr() method. Takes option as it's second parameter.


match (pattern) (option): T

match<T, U>(pattern: pattern: Record<'Some'|'None', (x?: T) => U>): (option: Option<T>) => U

Curried operator alias of the match() method. Takes option as it's second parameter.


prop (property) (option): T

prop<T> (property: string): (option: Option<{[key: string]: T}>) => Option<T>

Extracts valueproperty from the option.

Returns None if option is None, or if value[property] is either null or undefined. Otherwise returns a Some of the extracted value.


filter (predicate) (option): Option

filter<T> (predicate: (x: T) => boolean): (option: Option<T>) => Option<T>

Calls predicate with the contained value.

Returns None if predicate is false, otherwise returns the option.


or (b) (a): Option

or<T> (b: Option<T>): (a: Option<T>) => Option<T>

Returns option a if it is a Some, otherwise returns option b.


orElse (f) (option): Option

orElse<T> (f: () => Option<T>): (option: Option<T>) => Option<T>

Returns the option if it is a Some, otherwise returns f().


and (b) (a): Option

and<T> (b: Option<T>): (a: Option<T>) => Option<T>

Returns option b if option a is a Some, otherwise None.