@gum-tech/flow-ts
TypeScript icon, indicating that this package has built-in type declarations

1.3.0 • Public • Published

Dominate exceptions and missing values in Typescript & Javascript.

What is flow-ts?

flow-ts is a library for Typescript and JavaScript that aims to safely handle exceptions and missing values, similar to how Rust handles them with its Option and Result types.

Why flow-ts?

In JS, null represents intentionally missing values, undefined represents unintentional missing values, and Exceptions are used for handling errors.

Rust skips using missing values and exceptions. Mainly to prevent issues and bugs like:

  • null pointer errors
  • runtime errors
  • unexpected behaviour
  • unhandled exceptions
  • sensitive data leakages through exceptions
  • race conditions
  • and so on.

Instead, Rust provides two special generic Option and Result to deal with the above cases.

flow-ts implements the Option & Result types for Typescript & Javascript.

Why should you use flow-ts?

There are already several excellent libraries that implement functional patterns in Typescript. Why flow-ts?

These libraries are usually general-purpose toolkits aiming to implement all the functional programming patterns and abstractions. flow-ts has a more focused goal. We wanted a library specifically to dominate safely handle exceptions and missing values (null, undefined). The same way as it’s implemented in Rust.

Other distinguishing features of flow-ts:

  • Zero dependencies: flow-ts has no external dependencies.
  • Practical: • Rather than bore you with all the Monad / Category theory talk, we focus on the practical applications of Monads in a way you can use today. Just as you don’t need to understand group theory to do basic arithmetic, you don’t need to understand monad theory to use flow-ts.
  • 100% functions: • Some other libraries implement Option and Result monad with OOP style and patterns under the hood. We choose an utterly functional approach. Immutability and side-effect-free functions are at the heart of flow-ts design philosophy.

Convinced?

Great! Let’s get started.

Installation

> npm install @gum-tech/flow-ts 

If you find this package useful, please click the star button !

Table of contents

Option<T>

Introduction

“Null has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.” - Tony Hoare, the inventor of null

null values can be difficult to detect and handle correctly. When a null value is encountered, it may not be immediately clear why it is there or how to handle it. This can lead to bugs that are hard to diagnose and fix.

Another problem with null values is that they can cause runtime errors if they are not properly handled. For example, if a program attempts to access a property of an object that is null, it will often raise a NullPointerException or similar error. These errors can be difficult to anticipate and debug, especially if they occur deep in the codebase or if there are many layers of abstraction involved.

To avoid these problems, we use Option monad as an alternative way of representing the absence of a value or the lack of an object reference.

A monad is a design pattern that allows for the creation of sequenced computations, or "actions," that can be combined in a predictable way.

The option monad is a specific type of monad that represents computations that may or may not return a value.

Option monad types allow for the explicit representation of the possibility of a missing value, and they provide methods for handling these cases in a predictable and composable way.

The option monad is usually implemented as an algebraic data type with two cases: Some and None. The Some case represents a computation that has a value, and it is parameterized by the type of the value. The None case represents a computation that has a missing value.

type Option<T> = None | Some<T>

Option monad helps us safely handle missing values in a predictable and composable way without being afraid of the null pointer exception, runtime errors, and unexpected behaviour in our code.

⬆️ Back to top

Basic usage

Example I

Let’s start with a common example you see in many codebases today.

interface User {
  id: number,
  fullname: string,
  username: string,
}

const users: User[] = [
  {
    id: 1, 
    fullname: "Leonardo Da Vinci",
    username: "leo"
  },
  {
    id: 2, 
    fullname: "Galileo Galilei",
    username: "gaga"
  }
]

function getUser(id: number): User | null {
  return users.find(user => user.id === id) ?? null;
}

function getUserName(id: number): string | null {
  const user = getUser(id);
  if (user === null) {
    return null;
  }
  return user.username;
}

const username = getUserName(1);

if (username !== null) {
  console.log(username);
} else {
  console.log("User not found");
}

This code focuses on telling the computer how to perform a task, step by step. It involves specifying the sequence of actions that the computer should take and the specific operations it should perform at each step.

The code also uses null to define missing values. Even with a simple example like this, it’s not immediately clear where the null is coming from when we check if the username is null. In large codebases, this can be a nightmare to diagnose and fix.

However, since this code style is more familiar and follows a more traditional control flow, it can be easier to understand for most programmers.

Let's rewrite this with a declarative style using flow-ts option monad

import { Option, Some, None, match } from 'flow-ts';

const getUser = (id: number): Option<User> => Some(users.find(user => user.id === id));

const getUserName = (id: number): Option<string> => getUser(id).map(user => user.username);

match(getUserName(2))({
    Some: username => console.log(username),
    None: () => console.log("User not found"),
})

This code style focuses on describing the input (the user's ID) and the desired output (the username). The match function handles the case where the user is not found by providing a default value (in this case, a message saying "User not found").

With flow-ts, we have successfully handled missing values in a predictable and composable way.

Example II

Let’s look at another example of using option to handle optional values.

if the value of an object can be empty or optional like the middle_nameof User in the following example, we can set its data type as an Optiontype.

import { Option, Some, None, match } from 'flow-ts';

interface User {
  firstName: string;
  middleName: Option<string>; // middle name can be empty
  lastName: string;
}

const getFullName = ({ firstName, middleName, lastName }: User): string => (

  match(middleName)({
    Some: mName => `${firstName} ${mName} ${lastName}`,
    None: () => `${firstName} ${lastName}`,
  })
)

getFullName({firstName: "Galileo", middleName: None, lastName: "Galilei"}); // Galileo Galilei	
getFullName({firstName: "Leonardo", middleName: Some("Da"), lastName: "Vinci"}); // Leonardo Da Vinci

Example III

Let’s look at another example by chaining calculations

import { Option, Some, None, match } from 'flow-ts';

const sine = (x: number): Option<number> =>  Some(Math.sine(x)); 
const cube = (x: number): Option<number> => Some(x * x * x);
const inc = (x: number): Option<number> => Some(x + 1); 
const double = (x: number): Option<number> => Some(x * x);
const divide = (x: number, y: number): Option<number> => y > 0 ? Some(x/y) : None

const sineCubedIncDoubleDivideBy10 = (x: number): Option<number> => 
                                                                  Some(x)
                                                                  .andthen(sine)
                                                                  .andthen(cube)
                                                                  .andthen(inc)
                                                                  .andthen(double)
                                                                  .andthen(divide);

match(sineCubedIncDoubleDivideBy10(30))({
  Some: result => console.log(`Result is ${result}`),
  None: () => console.log(`Please check your inputs`)
})

Example IV

Example III with map

import { Option, Some, None, match } from 'flow-ts';

const sine = (x: number): number =>  Math.sine(x); 
const cube = (x: number): number => x * x * x;
const inc = (x: number): number => x + 1; 
const double = (x: number): number => x * x;

const sineCubedIncDouble = (x: number): Option<number> => 
                                                        Some(x)
                                                        .map(sine)
                                                        .map(cube)
                                                        .map(inc)
                                                        .map(double)

match(sineCubedIncDouble(30))({
  Some: result => console.log(`Result is ${result}`),
  None: () => console.log(`Please check your inputs`)
}

⬆️ Back to top

Benefits

There are several reasons why you might want to use the option monad in your code:

  1. To avoid null reference exceptions: As mentioned earlier, the option monad is a way of representing optional values in a type-safe way. This can help you avoid null reference exceptions by allowing you to explicitly handle the absence of a value in your code.
  2. To make your code more readable: Using the option monad can make your code more readable, because it clearly indicates when a value may be absent. This can make it easier for other developers to understand your code and can reduce the need for comments explaining how null values are handled.
  3. To improve code reliability: By explicitly handling the absence of a value, you can make your code more reliable and less prone to runtime errors.
  4. To improve code maintainability: Using the option monad can make your code more maintainable, because it encourages a clear and explicit handling of optional values. This can make it easier to modify and extend your code in the future.
  5. To make you write code that is more declarative and less imperative. This can make your code easier to understand and test.

⬆️ Back to top

API

flow-ts Option exposes the following:

  • isSome returns true if the option is a Some value, false otherwise.
  • isNone returns true if the option is a None value, false otherwise.
  • unwrap extract the value out of Option<T>. It will raise an error if you call it on None. You can use unwrapOr for safe unwrapping.
  • unwrapOr has one argument of the same type as T in Option<T>. It unwraps the value in case of Some or returns the argument value back in case of None. It's a safe and recommended way of extracting values.
  • expect similar to unwrap but can accept an argument for setting a custom message for the error.
  • unwrapOrElse similar to unwrapOr. The only difference is, instead of passing a value, you have to pass a closure which returns a value with the same data type as T in Option<T>
  • okOr transform Option type into Resulttype. Some to Ok and None to Err. A default error message must be passed as an argument.
  • map allows for the transformation of the value contained in a **Some**instance. E.g Option<T>to Option<U>. Only Some values are getting changed - no effect to None.
  • andthen  allows for safe chaining of multiple option monad computations and returns Noneif the option is None anywhere on the chain.
  • or when combining two expressions. If either one got Some, that value returns immediately.
  • orElse Similar to or. The only difference is that the second expression should be a closure that returns the same type T
  • and when combining two expressions, If both got Some, the value in the second expression returns. If either one got None that value returns immediately.
  • filter accepts a predicate as an argument. The predicate uses the value inside Some as an argument. The same Some type is returned only if we pass a Some value and the predicate returns true. None is returned if a None type is passed or the predicate returns false.
import {
  isSome,
  isNone,
  unwrap, 
  unwrapOr,
  expect,
  unwrapOrElse,
  OkOr,
  andthen,
  or,
  and,
  map,
  filter,
  orElse
} from 'flow-ts'

⬆️ Back to top

API Documentation

isSome 

returns true if the option is a Some value, false otherwise.

Example

import { Some, None, isSome } from 'flow-ts'

isSome(Some(1)) // true
isSome(None), // false

isNone 

returns true if the option is a None value, false otherwise.

Example

import { Some, None, isNone } from 'flow-ts'

isNone(some(1)) // false
isNone(none) // true

unwrap 

extract the value out of Option<T>. It will raise an error if you call it on None. You can use unwrapOrfor safe unwrapping.

Throws a ReferenceError if the option is None.

Example

import { Some, None } from 'flow-ts'

Some("car").unwrap() // "car"
None.unwrap() // fails, throws an Exception

unwrapOr 

has one argument of the same type as T in Option<T>. It unwraps the value in case of Someor returns the argument value back in case of None. It's a safe and recommended way of extracting values.Signature

Example

import { Some, None } from 'flow-ts'

Some("car").unwrapOr("bike") // "car"
None.unwrapOr("bike") // "bike"

expect 

similar to unwrap()but can accept an argument for setting a custom message for the error.

Example

import { Some, None } from 'flow-ts'

Let n = None;
n.expect("empty value returned"); //ReferenceError: empty value returned

unwrapOrElse 

similar to unwrap_or(). The only difference is, instead of passing a value, you have to pass a closure which returns a value with the same data type as T in Option<T>

Example

import { Some, None } from 'flow-ts'

let fnElse = () => 16;

Some(8).unwrap_or_else(fnElse) // 8
None.unwrap_or_else(() => 'error returned') // 'error returned'

okOr 

transform Option type into Resulttype. Some to Ok and None to Err. A default error message must be pass as argument.

Example

import { Some, None, Option } from 'flow-ts'

const errDefault = "error message";

const s: Option<string> = Some("abc")
const n = None

s.okOr(errDefault) // Ok("abc")
n.okOr(errDefault) // Err("error message") 

map 

allows for the transformation of the value contained in a Some instance.

E.g Option<T>to Option<U>.

Only Some values are getting changed.

No effect to None.

Example

import { Some, None } from 'flow-ts'

const cube = (x: number) : number => x * x * x
Some(10).map(x => x + 1).map(cube).map(y => y * 2) // Some(2662)

andthen 

allows for safe chaining of multiple option monad computations. And returns Noneif the option is None anywhere on the chain.

Example

import { Some, None, Option } from 'flow-ts'

const sine = (x: number): Option<number> =>  Some(Math.sine(x)); 
const cube = (x: number): Option<number> => Some(x * x * x);
const inc = (x: number): Option<number> => Some(x + 1); 
const double = (x: number): Option<number> => Some(x * x);
const divide = (x: number, y: number): Option<number> => y > 0 ? Some(x/y) : None

const sineCubedIncDoubleDivideBy10 = (val: number): Option<number> => 
Some(val)
.andthen(sine)
.andthen(cube)
.andthen(inc)
.andthen(double)
.andthen(divide);

match(sineCubedIncDoubleDivideBy10(30))({
  Some: result => console.log(`Result is ${result}`),
  None: () => console.log(`Please check your inputs`)
})

or 

when combining two expressions If either one got Some, that value returns immediately.

Examples

import { Some, None, Option } from 'flow-ts'

let s1: Option<string> = Some("some1")
let s2: Option<string> = Some("some2")
let n = None; 

s1.or(s2); // => Some("some1")
s1.or(n); // => Some("some1")
n.or(s1); // => Some("some1")
n.or(n); // => None
import { Some, None } from 'flow-ts'

const inc = (x) => Some(x + 1)

Some(5).andthen(inc).or(Some(0)) // => Some(6)
Some(undefined).andthen(inc).or(Some(0)) // => Some(0)

orElse 

Similar to or. The only difference is, the second expression should be a closure which returns same type T.

Example

import { Some, None, Option } from 'flow-ts' 

let s1: Option<string> = Some("some1")
let sFn = () => Some("some2")
let n: Option<string> = None; 
let nFn = () => None

s1.orElse(sFn); // => Some("some1")
s1.orElse(nFn); // => Some("some1")
n.orElse(sFn); // => Some("some2")
n.orElse(nFn); // => None

and 

when combining two expressions If both got Some, the value in the second expression returns. If either one got Nonethat value returns immediately.

Example

import { Some, None, Option } from 'flow-ts' 

let s1: Option<string> = Some("some1")
let s2: Option<string> = Some("some2")
let n: Option<string> = None; 

s1.and(s2); // => Some("some2")
s1.and(n); // => None
n.and(s1); // => None
n.and(n); // => None

filter 

accepts a predicate as an argument. The same Sometype is returned, only if we pass a Somevalue and predicate returns true for it. Noneis returned, if None  type passed or the predicate returns false. The predicate uses the value inside Some  as an argument.

Example

import { Some, None, Option } from 'flow-ts'

let s1: Option<number> = Some(3);
let s2: Option<number> = Some(6);
let n = None;

const isEVen = (val: number): number => val % 2 == 0;

s1.filter(fn_is_even);  // Some(3) -> 3 is not even -> None
s2.filter(fn_is_even); // Some(6) -> 6 is even -> Some(6)
n.filter(fn_is_even);  // None -> no value ->

If you find this package useful, please click the star button !

⬆️ Back to top

Result<T, E>

Introduction

Exceptions are a mechanism for handling errors and exceptional circumstances in many programming languages. When an exception is thrown, the normal flow of control in the program is interrupted, and the program tries to find an exception handler to handle the exception. If no appropriate exception handler is found, the program may crash or produce unexpected results.

There are several problems with using exceptions for error handling:

  1. Exceptions can be difficult to anticipate: Exceptions can be thrown anywhere in the code, making it difficult to anticipate where they might occur and how to handle them. This can make it hard to write robust, reliable code.
  2. Exceptions can be hard to debug: When an exception is thrown, the normal flow of control in the program is interrupted, making it difficult to trace the cause of the exception and fix the error.
  3. Exceptions can make code harder to read: When exceptions are used extensively, the code can become cluttered with try-catch blocks, making it harder to understand what is happening.
  4. Exceptions can have performance overhead: Throwing and catching exceptions can have a significant performance overhead, especially if they are used extensively.

The result monad is a way to handle errors and exceptions in a more predictable and structured way. Instead of using exceptions, the result monad uses a variant-based approach, with separate Ok and Err variants representing successful and unsuccessful computations, respectively. This allows for more predictable error handling and makes it easier to anticipate and handle errors in the code.

type Result<T, E> = Ok<T, E> | Err<T, E>;

The result monad provides a more predictable and structured approach to error handling, which can improve the reliability, readability, performance, and composability of code.

⬆️ Back to top

Basic usage

Let’s start with an example of how you might use exceptions in Typescript.

function divide(numerator: number, denominator: number): number {
  if (denominator === 0) {
    throw new Error("Division by zero");
  }
  return numerator / denominator;
}

const addOne = (x: number): number => x + 1;

function compute(numerator: number, denominator: number): number {
  try {
    let result = divide(numerator, denominator);
    result = addOne(result);
    return result;
  } catch (error) {
    return 0;
  }
}

console.log(compute(10, 2)); // 6
console.log(compute(10, 0)); // 0

In this example, the divide function throws an exception if the denominator is zero, and the compute function uses a try-catch block to handle the exception and return zero if it occurs.

Let rewrite this with a declarative style using flow-ts result monad

import { Result, Ok, Err, match } from 'flow-ts';

function divide(numerator: number, denominator: number): Result<number, string> {
  if (denominator === 0) {
    return Err("Division by zero");
  }
  return Ok(numerator / denominator);
}

const addOne = (x: number): Result<number, string> => Ok(x + 1);

const compute = (numerator: number, denominator: number): number => divide(numerator, denominator).andthen(addOne)

match(compute(10, 2))({
    Ok: res => console.log(res),
    Err: () => console.log(0),
}) // 6
match(compute(10, 0))({
    Ok: res => console.log(res),
    Err: () => console.log(0),
}) // 0

In this example, the divide function returns a result monad representing the result of a division operation. If the denominator is zero, it returns an Err variant with an error message. If the denominator is non-zero, it returns an Ok variant holding the result of the division.

The addOne function takes a number and returns a result monad representing the result of adding one to that number. In this case, it always returns an Ok variant.

andthen is used to chain the divide and addOne functions together, passing the result of the divide function as input to the addOne function. If the divide function returns an Err variant, andThen short-circuits the chain and returns the Err variant immediately.

You can also use orElse to handle any errors that might occur in the computation. If the result monad is an Err variant, the provided fallback function is called with the error as input and its result is returned.

function divide(numerator: number, denominator: number): Result<number, string> {
  if (denominator === 0) {
    return Err("Division by zero");
  }
  return Ok(numerator / denominator);
}

const addOne = (x: number): Result<number, string> => Ok(x + 1);

const compute = (numerator: number, denominator: number): number =>
  divide(numerator, denominator)
    .andthen(addOne)
    .orElse((error: string) => Ok(0))
    .unwrap();

console.log(compute(10, 2)); // 6
console.log(compute(10, 0)); // 0

⬆️ Back to top

Benefits

There are several reasons why you might choose to use the result monad in your code:

  1. Improved error handling: The result monad provides a structured way to handle errors and exceptions, allowing for more predictable and easy-to-reason-about code.
  2. Improved code readability: By using the result monad, it is clear to anyone reading the code that a computation may or may not be successful, and what to do in each case. This can make the code easier to understand and maintain.
  3. Improved code reliability: By using the result monad, it is easier to ensure that errors and exceptions are properly handled and do not result in unexpected behavior or crashes.
  4. Improved code composability: The result monad allows for the chaining of operations, similar to the way that the Promise type in JavaScript allows for the chaining of asynchronous operations. This can make it easier to build up complex computations from simpler ones.

⬆️ Back to top

API

flow-ts Result exposes the following:

  • isOk returns true if the result is a Ok value, false otherwise.
  • isErr returns true if the option is a Err value, false otherwise.
  • ok transform Result type into Optiontype. Ok to Some and Err to None.
  • err transform Result type into Optiontype. Ok to None and Err to Some.
  • unwrap extracts the value held by Ok, or throws an error if it is Err
  • unwrapOr unwraps the value in case of Ok or returns the argument value back in case of Err.
  • unwrapOrElse similar to unwrapOr. The only difference is, instead of passing a value, you have to pass a closure.
  • unwrapErr extracts the error held by Err, or throws an error if it is Ok
  • expect similar to unwrap() but can accept an argument for setting a custom message for the error.
  • expectErr extracts the error held by Err, or throws an error if it is Ok
  • andthen allows for safe chaining of computations, where the result of one computation is passed as input to another computation. I
  • map allows for the transformation of the value held by Ok, leaving Err unchanged
  • mapErr allows for the transformation of the error held by Er, leaving Ok unchanged
  • and when combining two expressions, If both got Ok, the value in the second expression returns. If either one got Err that value returns immediately.
  • or when combining two expressions. If either one got Ok, that value returns immediately.
  • orElse allows for the handling of errors, by providing a fallback computation to be used in the case of an error ⬆️ Back to top

API Documentation

isOk

returns true if the result is a Ok value, false otherwise.

Example

import { Ok, Err, isOk } from 'flow-ts'

isOk(Ok(1)) // true
isOk(Err('error')), // false

isErr

returns true if the option is a Err value, false otherwise.

Example

import { Ok, Err, isErr } from 'flow-ts'

isErr(Err('error')) // false
isErr(Ok('not an error')) // true

ok

transform Result type into Optiontype. Ok to Some and Err to None.

Example

import { Ok, Err } from 'flow-ts'

Ok(10).ok() // Some(10)
Err('message').ok() // None 

err

transform Result type into Optiontype. Ok to None and Err to Some.

Example

import { Ok, Err } from 'flow-ts' 

Ok(10).err() // None
Err('message').err() // Some('message')

unwrap

extracts the value held by Ok, or throws an error if it is Err

Example

import { Ok, Err } from 'flow-ts'

Ok('car').unwrap() // "car"
Err('error').unwrap() // fails, throws an Exception

unwrapOr

unwraps the value in case of Ok or returns the argument value back in case of Err.

Example

import { Ok, Err } from 'flow-ts'

Ok("car").unwrapOr("bike") // "car"
Err('car').unwrapOr("bike") // "bike"

unwrapOrElse

similar to unwrapOr. The only difference is, instead of passing a value, you have to pass a closure.

Example

import { Ok, Err } from 'flow-ts'

let fnElse = () => 16;

Ok(8).unwrap_or_else(fnElse) // 8
Err('message').unwrap_or_else(() => 'error returned') // 'error returned'

unwrapErr

extracts the error held by Err, or throws an error if it is Ok

Example

import { Ok, Err } from 'flow-ts'

Ok(2).unwrapErr() // fails, throws an Exception
Err('message').unwrapErr() // message

expect

similar to unwrap() but can accept an argument for setting a custom message for the error.

Example

import { Err } from 'flow-ts'

Err('message').expect("empty value returned"); // ReferenceError: empty value returned
Ok(true).expect('Not true') // true

expectErr

extracts the error held by Err, or throws an error if it is Ok

Example

import { Ok, Err } from 'flow-ts'

Err('message').expectErr('error') // message
Ok('message').expectErr('error') // fails, throw an exception

andthen

allows for safe chaining of computations, where the result of one computation is passed as input to another computation.

Example

import { Ok, Err } from 'flow-ts'

Ok(2).andthen(x => Ok(x * 2)).unwrap() // 4 
Err('message').andthen(e => Ok(e)).unwrapErr() // message

map

allows for the transformation of the value held by Ok, leaving Err unchanged

Example

import { Ok, Err } from 'flow-ts'

Ok(2).map(x => x * 2).map(x => x * x * x).unwrap() // 64
Err('error').map(e => `map reached ${e}`).unwrapErr() // error

mapErr

allows for the transformation of the error held by Er, leaving Ok unchanged

Example

import { Ok, Err } from 'flow-ts'

Ok('abcde').mapErr(e => `map reached ${e}`).unwrap() // abcde
Err('error').mapErr(e => `mapErr reached ${e}`).mapErr(e => e.length).unwrapErr() // 20

and 

when combining two expressions, If both got Ok, the value in the second expression returns. If either one got Err that value returns immediately.

Example

import { Ok, Err } from 'flow-ts';

Ok('ok1').and(Ok('ok2')) // Ok('ok2')
Ok('ok1').and(Err('error1')) // Err('error1')
Err('error1').and(Ok('ok1')) // Err('error1')
Err('error1').and(Err('error2')) // Err('error1')

or 

when combining two expressions. If either one got Ok, that value returns immediately.

Example

import { Ok, Err } from 'flow-ts';

Ok('ok1').or(Ok('ok2') // Ok('ok1')
Ok('ok1')or(Err('error1')) // Ok('ok1')
Err('error1').or(Ok('ok1')) // Ok('ok1')
Err('error1').or(Err('error2')) // Err('error2')

orElse 

allows for the handling of errors, by providing a fallback computation to be used in the case of an error

Example

import { Ok, Err } from 'flow-ts';
      
Ok('ok1').orElse(() => Ok('ok2')) // Ok('ok1')
Ok('ok1').orElse(() => Err('error2')) // Ok('ok1')
Err('error1').orElse(() => Ok('ok2')) // Ok('ok2')
Err('error1').orElse(() => Err('error2')) // Err('error2')
  

⬆️ Back to top

Utils

Flatten

To remove many levels of nesting:

import { Some, None, Ok, Err, flatten } from 'flow-ts'

// with Option
flatten(Some(Some(None))) // None
flatten(Some('some1')) // Some('some1')
flatten(None) // None

// with Result 
flatten(Ok(Ok(Ok(Ok(Ok(Ok(Ok(10)))))))) // Ok(10)
flatten(Ok(Ok(Err('error1')))) // Err('error2')
flatten(Ok('ok1')) // Ok('ok1')
flatten(Err('error1')) // Err('error1')

Pattern matching

Matching Option:

import { Option, Some, None, match } from 'flow-ts';

const getUser = (id: number): Option<User> => Some(users.find(user => user.id === id));

const getUserName = (id: number): Option<string> => getUser(id).map(user => user.username);

match(getUserName(2))({
		Some: username => console.log(username),
	  None: () => console.log("User not found"),
})

Matching Result:

import { Result, Ok, Err, match } from 'flow-ts';

function divide(numerator: number, denominator: number): Result<number, string> {
  if (denominator === 0) {
    return Err("Division by zero");
  }
  return Ok(numerator / denominator);
}

const addOne = (x: number): Result<number, string> => Ok(x + 1);

const compute = (numerator: number, denominator: number): number => divide(numerator, denominator).andthen(addOne)

match(compute(10, 2))({
    Ok: res => console.log(res),
    Err: () => console.log(0),
}) // 6

⬆️ Back to top

If you find this package useful, please click the star button !

Package Sidebar

Install

npm i @gum-tech/flow-ts

Weekly Downloads

0

Version

1.3.0

License

MIT

Unpacked Size

92.9 kB

Total Files

18

Last publish

Collaborators

  • tomiwaadey