@avanzu/std v2.0.4
@avanzu/std
A rust inspired implementation of the Option and Result type with some additional functional flavor.
Usage
In genereal
- the
Optiontype can be used for values that may benullorundefined. - the
Resulttype can be used to express the outcome of a synchronous computation.
Option
The abstract Option type contains two concrete types that share an identical api.
Some<T>- the type that contains a concrete valueNone- the type that contains nothing
Adapting the corresponding rust example function
const { Option } = require('@avanzu/std')
const divide = (numerator, denominator) =>
denominator == 0 ? Option.None() : Option.Some(numerator / denominator)The return value of the function is an option.
let result = divide(2.0, 3.0)Option.fold(onNone, onSome)
Since javascript does not provide anything similar to rusts pattern matching, we need a different way to make sure that every branch is covered.
result.fold(
// the "None" branch must be handled
() => console.error('Cannot divide by 0'),
// before you can handle the "Some" branch
(x) => console.log('Result: ', x)
)Releasing the value out of the Option type works also quite similar.
Option.unwrap()
Some will return the wrapped value
Option.Some('FOO').unwrap()
// -> 'FOO'None will "panic" and throw an exception
Option.None().unwrap()
// -> Error('Unable to unwrap Option<None>.')Option.unwrapOr(defalut)
Some will return the wrapped value
Option.Some('FOO').unwrap()
// -> 'FOO'None will return the given default value
Option.None().unwrapOr('BAR')
// -> 'BAR'Option.unwrapOrElse(callable)
Some will return the wrapped value
Option.Some('FOO').unwrap()
// -> 'FOO'None will return the value from the given callable
Option.None().unwrapOrElse(() => 'BAZ')
// -> 'BAZ'Result
The abstract Result type contains two concrete types that share an identical api.
Ok<T>- the type that contains the value of a successful computationErr<U>- the type that contains the error of a failed computation
It behaves very similar to the Option type with the only difference that the Err branch can also hold a value which is typically an Error.
Adapting the corresponding rust example function
const { Result } = require('@avanzu/std')
const Version = {
Version1: 'Version1',
Version2: 'Version2',
}
const parseVersion = ([num] = []) => {
switch (num) {
case undefined:
return Result.Err('invalid header length')
case 1:
return Result.Ok(Version.Version1)
case 2:
return Result.Ok(Version.Version2)
default:
return Result.Err('invalid version')
}
}The return value of the function is a result.
let result = parseVersion([2])Result.fold(onErr, onOk)
Similar to the Option type, we can use fold to exhaust all branches.
result.fold(
// the "Err" branch must be handled
(e) => console.error('error parsing header', e),
// before you can handle the "Ok" branch
(x) => console.log('working with version: ', x)
)Releasing the value out of the Result type works also quite similar.
Result.unwrap()
Ok will return the wrapped value
Result.Ok('FOO').unwrap()
// -> 'FOO'Err will "panic" and throw an exception
Result.Err('Does not compute.').unwrap()
// -> Error('Does not compute.')Result.unwrapOr(defalut)
Ok will return the wrapped value
Result.Ok('FOO').unwrap()
// -> 'FOO'Err will return the given default value
Result.Err('Does not compute.').unwrapOr('BAR')
// -> 'BAR'Result.unwrapOrElse(callable)
Ok will return the wrapped value
Result.Ok('FOO').unwrap()
// -> 'FOO'Err will return the value from the given callable
Result.Err('Does not compute.').unwrapOrElse(() => 'BAZ')
// -> 'BAZ'Result.try(unsafeFunction)
You can use Result.try to wrap an "unsafe" function that may throw an exception.
const parse = Result.try(JSON.parse)With valid json, parse will return Ok<T> which you can unwrap.
parse('{"foo": "bar"}').unwrapOr({})
// { foo: 'bar' }With invalid json, parse will return Err<U> which you can ignore and return a default value.
parse('{"foo": "bar"').unwrapOr({})
// -> {}Promises
Both types provide a .promise() method that allows to transform seamlessly into a promise.
OptionSomewill transform into a resolved promiseNonewill transform into a rejected promise
ResultOkwill transform into a resolved promiseErrwill transform into a rejected promise
Transforming from a promise into a result is not that simple due to the synchronous nature of Result. However, you can use await in parenthesis to make it work.
const p = Promise.resolve('OK')
(await Result.promised(p)).unwrap()
// 'OK'important in order for this to work, you have to use parenthesis. Otherwise await will not know where the promise is supposed to end.
await Result.promised(p).unwrap()
// TypeError: Result.promised(...).unwrap is not a functionFunctional style
Both Option and Result provide some functional style methods for you to play with.
The following examples will only showcase with the
Resulttype but theOptiontype behaves identically.
Functor
T(a).map(a -> b) -> T(b)
You can safely use .map without worrying about null or error checks.
Result.Ok('foo')
.map((s) => s.toUpperCase())
.unwrapOr('')
// -> 'FOO'
Result.Err()
.map((s) => s.toUpperCase())
.unwrapOr('')
// -> ''Monad
T.chain(a -> T(b)) -> T(b)
You can safely .chain Results or Options
Result.Ok('foo')
.chain((s) => Result.Ok(`${s}bar`))
.unwrapOr('')
// -> foobar
Result.Ok('foo')
.chain((s) => Result.Err('Does not compute.'))
.unwrapOr('')
// -> ''Applicative
T(a -> b).ap(T(a)) -> T(b)
You can apply a function inside a Result to a value in another Result.
const concat = (a) => (b) => `${a}-${b}`
Result.Ok(concat).ap(Result.Ok('foo')).ap(Result.Ok('bar')).unwrapOr('')
// foo-bar
Result.Ok(concat).ap(Result.Ok('foo')).ap(Result.Err()).unwrapOr('')
// ''Bifunctor
T(a|b).bimap((b -> c), (a -> d)) -> T(d|c)
You can assign a callback to each branch simultaneously. Similar to .fold the first callback is used for the error case and the second one for the success case.
const onErr = (err) => new Error('Changed')
const onOk = (value) => value.toUpperCase()
Result.Ok('foo').bimap(onErr, onOk).fold(console.error, console.log)
// -> 'FOO
Result.Err().bimap(onErr, onOk).fold(console.error, console.log)
// -> Error: ChangedSemigroup
T(a).concat(T(b)) -> T(ab)
You can .concat an arbitrary number of results or options together, provided that the value they are holding is also a semigroup (provides a .concat method) like strings or arrays.
Result
Result.Ok('foo').concat(Result.Ok('bar')).unwrap()
// -> foobar
Result.Ok([1, 2])
.concat(Result.Ok([3, 4]))
.unwrap()
// -> [1,2,3,4]If you do concat with a Result.Err the outcome will remain the first one that occurred.
Result.Ok('test')
.concat(Result.Err('Error1'))
.concat(Result.Err('Error2'))
.fold(console.error, console.log)
// -> Error: Error1Option
Option.Some('foo').concat(Option.Some('bar')).unwrap()
// -> foobar
Option.Some([1, 2])
.concat(Option.Some([3, 4]))
.unwrap()
// -> [1,2,3,4]If you do concat with a Option.None the outcome will remain the previous one.
Option.Some('test')
.concat(Option.None())
.concat(Option.Some('!!!'))
.fold(console.error, console.log)
// -> test!!!