Option
APIOptional values
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
Method 1: Option.isNone
/ Option.isSome
(Recommended)
- ✓ 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
- ✓ 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)
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
Always use Option
type when possible
-
Avoid using
null
// ✓ GOOD
const someOptionFunc = () => Option.from(someNullableFunc()); // null -> undefined -
Option<V>
is more expressive thanV | undefined
and more readable, especially when combining with more union type
Use precise and meaningful functions
- Prefer
Option.map
/Option.andThen
/Option.orElse
when mapping anOption
to anOption
- 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
) ?
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
andundefined
- Every access to a property or array would have to be converted from
undefined
ornull
toNone|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
tonull
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
?
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.