Aller au contenu principal

Option

Optional values

API

Motivation

An Option<V> is either a value of type V or nothing. An empty value is represented by Option.None and a defined value by Option.Some(...). Internally, Option.None is undefined.

Usage

An enum can be declared as the following example :

import { Option } from '@w5s/core';

export type OptString = Option<string>;

const withSuffix = (opt: OptString) => Option.map(opt, (_) => `${_}_suffix`);
withSuffix(Option.Some('foo')); // Option.Some('foo_suffix')
withSuffix(Option.None); // Option.None

const withFallback = (opt: OptString) => Option.orElse(opt, () => 'fallback');
withFallback(Option.Some('foo')); // Option.Some('foo')
withFallback(Option.None);// Option.None

Matching on values

remarque
  • ✓ Best Expressiveness
  • ✓ Good performances
import { Option } from '@w5s/core';

const optionToString = <V>(option: Option<V>) => Option.isSome(option) ? `Some(${v})` : 'None');
optionToString(Option.Some(1));// 'Some(1)'
optionToString(Option.None);// 'None'

Method 2: Option.match

remarque
  • ✓ Good Expressiveness
  • ⚠️ Lower performances
import { Option } from '@w5s/core';

const optionToString = <V>(option: Option<V>) => Option.match(option, {
Some: (v) => `Some(${v})`,
None: () => 'None',
});
optionToString(Option.Some(1));// 'Some(1)'
optionToString(Option.None);// 'None'

Method 3: === undefined / !== undefined (i.e. inlining isNone / isSome)

remarque

Not recommended for an application, but only for a third party library.

  • ⚠️ Low expressiveness
  • ✓ Highest performances
  • ✓ No module load overhead
import type { Option } from '@w5s/core';

const optionToString = <V>(option: Option<V>) => option === undefined ? `Some(${v})` : 'None';

Coding Guide

astuce

Always use Option type when possible

  • Avoid using null

    // ✓ GOOD
    const someOptionFunc = () => Option.from(someNullableFunc()); // null -> undefined
  • Option<V> is more expressive than V | undefined and more readable, especially when combining with more union type

astuce

Use precise and meaningful functions

  • Prefer Option.map / Option.andThen / Option.orElse when mapping an Option to an Option
  • Prefer ternary operators over if / else
// ✓ OK
const myFunc = <V>(option: Option<V>) => Option.map(option, (value) => /* ... */);

// = OK with caution
const myFunc = <V>(option: Option<V>) => Option.isNone(option) ? /* ... */ : /* ... */;
// ⚠️ Be careful to return the same type in both cases

// ⤫ BAD
const myFunc = <V>(option: Option<V>) => {
if (Option.isNone(option)) {
return /* ... */ // Risk of returning a different type on both branches
}
return /* ... */
};

FAQ

Why choose undefined instead of null or a variant object (like fp-ts) ?

SOLUTION 1 : Tagged variant { _: 'None' } | { _: 'Some', value, } :

PROS :

  • Generic pattern matching

CONS :

  • Creates a third "nullable" representation after null and undefined
  • Every access to a property or array would have to be converted from undefined or null to None|Some()

SOLUTION 2 : null as None :

PROS :

  • JSON friendly

CONS :

  • `typeof null == 'object'``
  • Every access to a property or array would have to be converted from undefined to null

SOLUTION 3 : undefined as None :

PROS :

  • array and property access are already well typed
  • typeof undefined == 'undefined'

CONS :

  • undefined does not exist in JSON

Why choose the name Option over Maybe ?

It is a matter of preference. Rust uses Option, Haskell uses Maybe. Generally speaking, W5S packages naming tends to be often aligned with the Rust naming when no ECMA equivalent exists.