1.1.1 • Published 2 years ago

rorjs v1.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

rorjs

Typescript implementation for Option and Result based on Rust's Result and Option.

Contents

Installation

$ npm install rorjs

or

$ yarn add rorjs

Usage

Result

The Result<T, E> type encapsulates a value of type T with a possible error of type E. Let's see some examples.

Creation

To create an Ok result:

const okResult = ok(1);

To create an Error result:

const errResult = err("Wrong input!");

Both ok and err values can be of any type. In this case the Ok value type is number and Err value type is string. We could make an Err with the built in Javascript Error type:

const errResult = err(new Error("Wrong input!"));

Type safety and Narrowing

Let's say we are working with a Result<number, Error>:

const result: Result<number, Error> = returnsResult();

if(result.isOk()){
  // Typescript now knows that this result is of type Ok<number>
  // We can work safely with it
  console.log(result.getVal());
} else {
  // Typescript narrows the type and knows that is of type Err<Error>
  // We can controll the Error freely
  console.error(result.getErr());
}

// In contrast, we could check if it's Err
if(result.isErr()){
  // Typescript now knows that this result is of type Err<Error>
  // We can controll the Error freely
  console.error(result.getErr());
} else {
  // Typescript narrows the type and knows that is of type Ok<number>
  // We can work safely with it
  console.log(result.getVal());
}

getVal and getErr methods are only available when Typescript knows the concrete type of the Result for Ok and Err respectively.

Unwrap

The unwrap method extracts the contained value in Result<T, E> when is Ok(t). In case of Err it throws the contained error.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myOkVal = okResult.unwrap();
console.log(myOkVal); // Prints 1

const myWrongVal = errResult.unwrap(); // Throws "Wrong input!"

UnwrapOr

The unwrapOr method extracts the contained value in Result<T, E> when is Ok(t). In case of Err it returns the provided value.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myOkVal = okResult.unwrapOr(2);
console.log(myOkVal); // Prints 1

const myWrongVal = errResult.unwrapOr(2);
console.log(myWrongVal); // Prints 2

UnwrapOrElse

The UnwrapOrElse method extracts the contained value in Result<T, E> when is Ok(t). In case of Err it returns the result of evaluating the provided function.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myOkVal = okResult.unwrapOrElse(() => 2);
console.log(myOkVal); // Prints 1

const myWrongVal = errResult.unwrapOrElse(() => 2);
console.log(myWrongVal); // Prints 2

Map

The map method transforms Result<T, E> to Result<U, E> by applying the provided function f to the contained value of Ok(t) and leaving Err values unchanged.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myOkVal = okResult
  .map((val) => val * 2).unwrap();

console.log(myOkVal); // Prints 2

const myWrongVal = errResult
  .map((val) => val * 2).unwrap(); // Throws "Wrong input!"

MapErr

The mapErr method transforms Result<T, E> to Result<T, F> by applying the provided function f to the contained value of Err(err) and leaving Ok values unchanged.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myOkVal = okResult
  .mapErr(
    (err) => err.message + " Value must be greater than 5!"
  )
  .unwrap();

console.log(myOkVal); // Prints 1

const myWrongVal = errResult
  .mapErr(
    (err) => err.message + " Value must be greater than 5!"
  )
  .unwrap(); // Throws "Wrong input! Value must be greater than 5!"

AndThen

The andThen calls f if the result is Ok, otherwise returns the Err value of self.

This function can be used for control flow based on Result values.

const x = ok(1);
x.andThen((val) => ok(val + 1)).unwrap() // Yields 2

const x = ok(1);
x.andThen(() => err("late error")).unwrap() // Throws "late error"

const x = err("early error");
x.andThen((val) => ok(val + 1)).unwrap() // Throws "early error"

const x = err("early error");
x.andThen(() => err("late error")).unwrap() // Throws "early error"

AndThenAsync

The andThenAsync is analogous to andThen method but taking an async function and returning a Promise.

OrElse

The orElse calls f if the result is Err, otherwise returns the Ok value of self.

This function can be used for control flow based on Result values.

const x = ok(1);
x.orElse((err) => ok(2)).unwrap() // Yields 1

const x = ok(1);
x.orElse((err) => err("late error")).unwrap() // Yields 1

const x = err("early error");
x.orElse((err) => ok(1)).unwrap() // Yields 1

const x = err("early error");
x.orElse((err) => err("late error")).unwrap() // Throws "late error"

OrElseAsync

The orElseAsync is analogous to orElse method but taking an async function and returning a Promise.

Ok method

The ok method transforms Result<T,E> into Option<T>, mapping Ok(v) to Some(v) and Err(e) to None.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const mySomeVal: Some<number> = okResult.ok();
const myNone: None = myWrongVal.ok();

Err method

The err method transforms Result<T,E> into Option<E>, mapping Err(e) to Some(e) and Ok(v) to None.

const okResult = ok(1);
const errResult = err(new Error("Wrong input!"));

const myNone: None = okResult.err();
const mySomeErr: Some<Error> = myWrongVal.err();

And operator method

The and method treats the Result as a boolean value, where Ok acts like true and Err acts like false.

The and method can produce a Result<U, E> value having a different inner type U than Result<T, E>.

const x = ok(2);
const y = err("late error");
x.and(y).unwrap() // Throws "late error"

const x = err("early error");
const y = ok(2);
x.and(y).unwrap() // Throws "early error"

const x = err("not a 2");
const y = err("late error");
x.and(y).unwrap() // Throws "not a 2"

const x = ok(2);
const y = ok("hello world");
x.and(y).unwrap() // Yields "hello world"

Or operator method

The or method treats the Result as a boolean value like the and method.

The or method can produce a Result<T, F> value having a different error type F than Result<T, E>.

const x = ok(2);
const y = err("late error");
x.or(y).unwrap() // Yields 2

const x = err("early error");
const y = ok(2);
x.or(y).unwrap() // Yields 2

const x = err("not a 2");
const y = err("late error");
x.or(y).unwrap() // Throws "late error"

const x = ok(2);
const y = ok("hello world");
x.or(y).unwrap() // Yields 2

transposeResult

The transposeResult function transforms a Result of an Option into an Option of a Result.

Ok(None) will be mapped to None. Ok(Some(_)) and Err(_) will be mapped to Some(Ok(_)) and Some(Err(_)).

const r = ok(some(2));
const o = transposeResult(r);
// Yields Some(Ok(2))

const r = ok(none());
const o = transposeResult(r);
// Yields None

const r = err(some("error"));
const o = transposeResult(r);
// Yields Some(Err("error"))

Result combinator functions

These functions combine multiple Result's into a single Result output

allResults

The allResults function evaluates a set of Results. Returns an Ok with all Ok values if there is no Error. Returns Error with the first evaluated error result.

const x = ok(2);
const y = ok("hello");
const z = ok({ foo: "bar" });
allResults(x, y, z) // Yields Ok<[2, "hello", { foo: "bar" }]>

const x = ok(2);
const y = ok("hello");
const z = err("late error");
allResults(x, y, z) // Yields Err<"late error">

const x = err("early error");
const y = ok(2);
const z = err("late error");
allResults(x, y, z) // Yields Err<"early error">
anyResults

The anyResults function evaluates a set of Results. Returns an Ok with the first result evaluated is Ok. If no Ok is found, returns an Error containing the collected error values.

const x = ok(2);
const y = ok("hello");
const z = ok({ foo: "bar" });
anyResults(x, y, z) // Yields Ok<2>

const x = err("early error");
const y = ok("hello");
const z = ok(2);
anyResults(x, y, z) // Yields Ok<"hello">

const x = err("early error");
const y = err("oops");
const z = err("late error");
anyResults(x, y, z) // Yields Err<["early error", "oops", "late error"]>
tryAllResults

The tryAllResults function is a little bit more especial. valuates a set of Results wether they are Err or Ok. Returns an array of Options with all encountered Err. If all values are Ok, returns a tuple combining all values.

const x = ok(2);
const y = ok("hello");
const z = ok({ foo: "bar" });
anyResults(x, y, z) // Yields Ok<[2, "hello", { foo: "bar" }]>

const x = ok("hello");
const y = err("early error");
const z = ok(2);
anyResults(x, y, z) // Yields Err<[None, Some<"early error">, None]>

const x = err("early error");
const y = err("oops");
const z = err("late error");
anyResults(x, y, z) // Yields Err<[Some<"early error">, Some<"oops">, Some<"late error">]>

Option

The Option<T> type encapsulates a value of type T wich can be possibly undefined. Is a superposition of types Some<T> and None

Creation

To create a Some option:

const someValue = some(1);

To create a None option:

const noneValue = none();

To create an Option from a value that you don't know if it's actually defined or not use the optionFrom function:

const possiblyUndefinedValue = suspiciousFunction();

const option = optionFrom(possiblyUndefinedValue);

Type safety and Narrowing

Let's say we are working with a Option<number>:

const opt: Option<number> = returnsOption();

if(result.isSome()){
  // Typescript now knows that this option is of type Some<number>
  // We can work safely with it
  console.log(result.getVal());
} else {
  // Typescript narrows the type and knows that is of type None
}

// In contrast, we could check if it's Err
if(result.isNone()){
  // Typescript now knows that this result is of type None
} else {
  // Typescript now knows that this option is of type Some<number>
  // We can work safely with it
  console.log(result.getVal());
}

getVal method is only available when Typescript knows the concrete type of the Option for Some.

Unwrap

The unwrap method extracts the contained value in Option<T> when is Some. If is None throws error with generic message.

const someOption = some(1);
const noneOption = none();

const value = someOption.unwrap();
console.log(value); // Prints 1

const noneValue = errResult.unwrap(); // Throws OptionUnwrapError

Expect

The expect method extracts the contained value in Option<T> when is Some. If is None throws an Error with the provided message.

const someOption = some(1);
const noneOption = none();

const value = someOption.expect();
console.log(value); // Prints 1

const noneValue = errResult.expect("Oops!"); // Throws "Oops!"

UnwrapOr

The unwrapOr method extracts the contained value in Option<T> when is Some(t). In case of None it returns the provided value.

const someOption = ok(1);
const noneOption = none();

const someValue = someOption.unwrapOr(2);
console.log(someValue); // Prints 1

const noneValue = noneOption.unwrapOr(2);
console.log(noneValue); // Prints 2

UnwrapOrElse

The UnwrapOrElse method extracts the contained value in Option<T> when is Some(t). In case of None it returns the result of evaluating the provided function.

const someOption = ok(1);
const noneOption = none();

const someValue = someOption.unwrapOrElse(() => 2);
console.log(someValue); // Prints 1

const noneValue = noneOption.unwrapOrElse(() => 2);
console.log(noneValue); // Prints 2

OkOr

The okOr method transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(err).

Arguments passed to okOr are eagerly evaluated; if you are passing the result of a function call, it is recommended to use okOrElse, which is lazily evaluated.

const someOption = ok(1);
const noneOption = none();

const okResult: Result<number, string> = someOption.okOr("an error");
console.log(okResult.unwrap()); // Prints 1

const noneValue: Result<number, string> = noneOption.okOr("an error");
console.log(noneValue.unwrap()); // Throws "an error"

OkOrElse

The okOrElse method transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(f()).

const someOption = ok(1);
const noneOption = none();

const okResult: Result<number, string> = someOption.okOrElse(() => "an error");
console.log(okResult.unwrap()); // Prints 1

const noneValue: Result<number, string> = noneOption.okOrElse(() => "an error");
console.log(noneValue.unwrap()); // Throws "an error"

Map

The map method transforms Option<T> to Option<U> by applying the provided function f to the contained value of Some(t) and leaving None values unchanged.

const someOption = ok(1);
const noneOption = none();

const someValue = someOption
  .map((val) => val * 2).unwrap();

console.log(someValue); // Prints 2

const noneValue = noneOption
  .map((val) => val * 2).unwrap(); // Throws OptionUnwrapError

Filter

The filter method calls the provided predicate function f on the contained value t if the Option is Some(t), and returns Some(t) if the function returns true; otherwise, returns None.

const someOption = ok(1);
const noneOption = none();

const someValue = someOption
  .filter((val) => true).unwrap();

console.log(someValue); // Prints 2

const filteredValue = someOption
  .filter((val) => false).unwrap(); // Throws OptionUnwrapError

const noneValue = noneOption
  .filter((val) => true).unwrap(); // Throws OptionUnwrapError

const noneFilteredValue = noneOption
.filter((val) => false).unwrap(); // Throws OptionUnwrapError

AndThen

The andThen returns None if the option is None, otherwise calls f with the wrapped value and returns the result.

Some languages call this operation flatmap.

const x = some(1);
x.andThen((val) => some(val + 1)).unwrap() // Yields 2

const x = some(1);
x.andThen(() => none()).unwrap() // Throws OptionUnwrapError

const x = none();
x.andThen((val) => some(val + 1)).unwrap() // Throws OptionUnwrapError

const x = none();
x.andThen(() => none()).unwrap() // Throws OptionUnwrapError

AndThenAsync

The andThenAsync is analogous to andThen method but taking an async function and returning a Promise.

OrElse

The orElse returns the option if it contains a value, otherwise calls f and returns the result.

const x = some(1);
x.orElse(() => some(2)).unwrap() // Yields 1

const x = some(1);
x.orElse(() => none()).unwrap() // Yields 1

const x = none();
x.orElse(() => some(1)).unwrap() // Yields 1

const x = none();
x.orElse(() => none()).unwrap() // Throws OptionUnwrapError

OrElseAsync

The orElseAsync is analogous to orElse method but taking an async function and returning a Promise.

Zip

The zip method returns Some([x, y]) if this is Some(x) and the provided Option value is Some(y); otherwise, returns None

const x = some(1);
const y = some("hello");
console.log(x.zip(y).unwrap()); // Prints [1, "hello"]

const x = some(1);
const y = none();
console.log(x.zip(y).unwrap()); // Throws error

const x = none();
const y = some(1);
console.log(x.zip(y).unwrap()); // Throws error

const x = none();
const y = none();
console.log(x.zip(y).unwrap()); // Throws error

Flatten

The flatten method removes one level of nesting from an Option<Option<T>>

const opt1: Option<Option<number>> = some(some(1));
const flat1 = opt1.flatten(); // Yields Some(1);

const opt2: Option<Option<number>> = some(none());
const flat2 = opt2.flatten(); // Yields None;

const opt3: Option<Option<number>> = none();
const flat3 = opt3.flatten(); // Yields None;

And operator method

The and method treats the Option as a boolean value, where Some acts like true and None acts like false.

const x = some(2);
const y = none();
x.and(y).unwrap(); // Throws error

const x = none();
const y = some(2);
x.and(y).unwrap(); // Throws error

const x = none();
const y = none();
x.and(y).unwrap(); // Throws error

const x = some(2);
const y = some("hello world");
x.and(y).unwrap(); // Yields "hello world"

Or operator method

The or method treats the Option as a boolean value like the and method.

const x = some(2);
const y = none();
x.or(y).unwrap(); // Yields 2

const x = none();
const y = some(2);
x.or(y).unwrap(); // Yields 2

const x = none();
const y = none();
x.or(y).unwrap() // Throws "late error"

const x = some(2);
const y = some("hello world");
x.or(y).unwrap() // Yields 2

Xor operator method

The xor method treats the Option as a boolean value like the and and or methods.

const x = some(2);
const y = none();
x.xor(y).unwrap(); // Yields 2

const x = none();
const y = some(2);
x.xor(y).unwrap(); // Yields 2

const x = none();
const y = none();
x.xor(y).unwrap() // Throws "late error"

const x = some(2);
const y = some("hello world");
x.xor(y).unwrap() // Throws "late error"

transposeOption

The transposeOption function transforms a Option of a Result into an Result of an Option.

None will be mapped to Ok(None). Some(Ok(_)) and Some(Err(_)) will be mapped to Ok(Some(_)) and Err(_).

const r = some(ok(2));
const o = transposeOption(r);
// Yields Ok(Some(2))

const r = some(err("error"));
const o = transposeOption(r);
// Yields Err("error")

const r = none();
const o = transposeOption(r);
// Yields Ok(None)

Option combinator functions

These functions combine multiple Option's into a single Option output

allOptions

The allOptions function evaluates a set of Options. Returns a Some with all Some values if there is no None. Returns None with the first evaluated None result.

const x = some(2);
const y = some("hello");
const z = some({ foo: "bar" });
allOptions(x, y, z) // Yields Some<[2, "hello", { foo: "bar" }]>

const x = some(2);
const y = some("hello");
const z = none();
allOptions(x, y, z) // Yields None

const x = none();
const y = some(2);
const z = none();
allOptions(x, y, z) // Yields None
anyOptions

The anyOptions function evaluates a set of Results. Returns a Some with the first evaluated Somevalue. Returns None if no Some values are found.

const x = some(2);
const y = some("hello");
const z = some({ foo: "bar" });
anyOptions(x, y, z) // Yields Some<2>

const x = none();
const y = some("hello");
const z = some(2);
anyOptions(x, y, z) // Yields Some<"hello">

const x = none();
const y = none();
const z = none();
anyOptions(x, y, z) // Yields None

Motivation

Traditionally a function that could possibly return an error would be:

function getSquareRoot(num: number): number {
  if (num < 0) throw new Error("Invalid negative number")

  return Math.sqrt(num)
}

// The API tell's us that this function returns a number
const mySqr = getSquareRoot(-2)

And this is OK but when we expose this square root API the user does not know wether this function throws an error or what type of error it is.

If we encapsulate the return value in a Result type:

function getSquareRoot(num: number): Result<number> {
  if (num < 0) return error(new Error("Invalid negative number"))

  return ok(Math.sqrt(num))
}

// mySqr is not a number. Is a Result<number, Error>
// So in order to use it we must check explicitely if it is a valid value or an Error
const mySqr = getSquareRoot(-2)

Here we are returning a Result wrapping the error we would have throught with the error() function. And returning an Ok result wrapping the valid value with the ok() function.

The value of mySqr is not directly accesible since we don't know if it contains a valid value or error. We need to explicitely check:

const mySqr = getSquareRoot(4)

if(mySqr.isOk()){
  // TS Compiler narrows value
  // getValue() is available to get the wrapped value
  console.log(mySqr.getValue()) // Prints: 2
} else {
  // Otherwise getErr() is available to get the wrapped error
  const error = mySqr.getErr()

  // Treat the error
  throw error
}

Another solution is to use the unwrap() method, wich if it is an Ok Result returns the value or in case of an Error Result throws the error as a traditional exception.

const okResult = getSquareRoot(4)
const errorResult = getSquareRoot(-2)

console.log(okResult.unwrap()) // Prints: 2

console.log(errorResult.unwrap()) // Throws exception

Custom error types

function getSquareRoot(num: number): Result<number, InvalidSqrInput> {
  if (num < 0) return error(new InvalidSqrInput(num))

  return ok(Math.sqrt(num))
}

Note that with this type of Result we are always aware of the different types of errors that the API could return.

1.1.1

2 years ago

1.1.0

2 years ago

1.0.0

2 years ago