@iwburns/tupperware v2.0.0
tupperware
A library for safely and consistently dealing with complex values in Javascript and Typescript.
Overview
This library provides two types for dealing with optional / conditional values:
- Optional - A type representing an optional value.
- Result - A type representing the result of some fallible computation.
Optional
A value of this type is either a Some or a None. Some-values contain a value internally while None-values represent the absence of a given value. This is useful when you have a value that may or may not exist. Where you might otherwise use null or undefined to represent the absense of a value, you can use a None value instead.
Consider getting a property of an object that may or may not exist:
Normally you might do this:
const data = getSomeData(); // we may not know what data looks like here
let c = 0; // make sure you default it
if (typeof data.c !== 'undefined' && data.c !== null) { // make sure data.c exists
c = data.c;
}
doSomething(c); // now we can use cInstead you could do this:
const data = getSomeData();
let optC = getProperty(data, 'c'); // returns an Optional
let c = optC.unwrapOr(0); // we can use unwrapOr to safely get the value or
// a default value if c wasn't present on data
doSomething(c);Or if we want to avoid doing anything when c doesn't exist:
const data = getSomeData();
let optC = getProperty(data, 'c'); // returns an Optional
optC.forEach(doSomething); // forEach will call doSomething with optC's internal
// value if it exists, otherwise nothing happensThe key to this being useful is that both Some and None are wrapped in the same API. This means you can call forEach and unwrapOr (and a bunch of other methods) on the return value regardless of whether it's a Some or a None.
Note: This library doesn't provide a getProperty() function but one could imagine it looking something like:
function getProperty(obj, propName) {
if (typeof obj[propName] !== 'undefined' && obj[propName] !== null) {
return Optional.some(obj[propName]); // return a `Some`-value containing the value internally
}
return Optional.none(); // otherwise return a `None`-value
}Result
A value of this type is either an Ok or an Err. Both of these contain an internal value, but they each convey a different meaning. Ok is used to represent an operation that succeeded and returned some kind of successful result. Err is used to represent an operation that failed and returned some kind of error.
Consider parsing a number out of a string:
Normally you might do this:
const aNumber = getANumber(); // we may not know if this is a valid number
const result = parseInt(aNumber, 10); // do our parsing
let parsed = 0; // default it
if (!Number.isNaN(result)) { // if it didn't fail, hold on to the parsed value
parsed = result;
}
doSomething(parsed); // now we can use itInstead you could do this:
const aNumber = getANumber(); // we may not know if this is a valid number
const result = safeParseInt(aNumber, 10); // returns a Result
const parsed = result.unwrapOr(0); // we can use unwrapOr to safely get the value or
// a default value if the result was an Err-value
doSomething(parsed); // now we can use itOr if you want to handle both cases explicitly:
const aNumber = getANumber(); // we may not know if this is a valid number
const result = safeParseInt(aNumber, 10); // returns a Result
// result.match will call the given ok function if the result is an Ok-value
// and it will call the given err function if it is an Err-value
result.match({
ok: value => { doSomething(value); }, // pass the parsed value to `doSomething`
err: error => { console.error(error); }, // or do whatever you need to do with the error
});Again, the key here is that both Ok and Err values are wrapped in the same API. This means you can treat them both the same and just describe how you want them to behave instead of writing all of the boiler-plate logic every time you deal with them.
Note: This library doesn't provide a safeParseInt() function but it might look something like this if it were provided:
function safeParseInt(num, radix) {
let parsed = parseInt(num, radix);
if (Number.isNaN(parsed)) {
// return an Err-value with a meaningful error message
return Result.Err(`Could not parse the value: ${num} as an integer with radix: ${radix}`);
}
// otherwise return an Ok-value with the parsed value inside
return Result.Ok(parsed);
}