fat-arrow-ts
    TypeScript icon, indicating that this package has built-in type declarations

    0.9.0 • Public • Published

    => Fat Arrow · GitHub license npm version CircleCI Status

    Fat Arrow is a library for Typed Functional Programming in TypeScript compatible with Node.js and all major browsers.

    ⚠️ Alpha release! API may change ⚠️

    Installation

    npm install fat-arrow-ts   

    Setup Jest matchers

    Be sure to have a reference to a setup file in your jest.config.ts

    // In jest.config.ts
    
    export default {
    	setupFilesAfterEnv: [
    	  './setupTests.ts'
    	],
    }

    Include this in your setup file

    // In setupTests.ts
    
    import 'fat-arrow-ts/jest-matchers'

    Quick start

    import { left, right, Either } from 'fat-arrow-ts';  
      
    const getDivision = (numerator: number, denominator: number): Either<Error, number> => {  
        if (denominator === 0) {  
            return left(new Error('Division by zero!'))  
        }  
      
        return right(numerator / denominator)
    }
      
    const addTwo = (number: number) => number + 2  
      
    const print = (value: Either<Error, number>) =>  
        value.fold(  
            (error) => console.error('Doh!', error.message),  
            (result) => console.log(`Result is ${result}. Hooray!`)  
        )  
      
    print(getDivision(10, 0).flatMap(addTwo)) // Doh! Division by zero!  
    print(getDivision(10, 5).flatMap(addTwo)) // Result is 4. Hooray!  

    Essentials

    Flattening

    Fat Arrow's factory functions and data types' methods support flattening to accept both TS native values and data types themselves; in the latter case, data types objects will be flattened.

    import { right } from 'fat-arrow-ts';  
      
    const myValue = right<Error, number>(5)  
      
    console.log(right(myValue).equals(myValue)) // true  

    Maybe

    An Maybe<A> value is useful to model nullable values.

    maybe

    Takes a value in input and creates a Maybe<A> object.

    • if the input value is non-nullable the produced object will have just state.
    • if the input value is nullable (null | undefined) the produced object will have none state;
    import { Maybe, maybe } from 'fat-arrow-ts';  
      
    const myMap = new Map([  
        ['key1', 'value1'],  
        ['key2', 'value2'],  
    ])  
      
    const getValue = (key: string): Maybe<string> => maybe(myMap.get(key))
    
    //-- If value is "just" --//
    
    const existing = getValue('key1')  
      
    console.log(existing.fold()) // 'value1'  
    console.log(existing.isJust) // true  
      
    // Flattening  
    console.log(maybe(existing).equals(existing)) // true  
    console.log(maybe(existing).isJust) // true  
    
    //-- If value is "none" --//
      
    const missing = getValue('foo')  
      
    console.log(missing.fold()) // undefined  
    console.log(missing.isNone) // true  
      
    // Flattening  
    console.log(maybe(missing).equals(missing)) // true  
    console.log(maybe(missing).isNone) // true  

    just

    Takes a non-nullable value in input and creates a Maybe<A> object with just state.

    import { just, none, maybe } from 'fat-arrow-ts';  
      
    const myValue = just(5)  
      
    console.log(myValue.fold()) // 5  
    console.log(myValue.isJust) // true  
      
    // Flattening  
    console.log(maybe(myValue).equals(myValue)) // true  
    console.log(just(myValue).equals(myValue)) // true  

    none

    Creates a Maybe<A> object with none state.

    import { just, none, maybe } from 'fat-arrow-ts';  
      
    const myValue = none()  
      
    console.log(myValue.fold()) // null 
    console.log(myValue.isLeft) // true
    console.log(myValue === none()) // true
      
    // Flattening  
    console.log(maybe(myValue).equals(myValue)) // true  
    console.log(just(myValue).equals(myValue)) // true  

    Maybe API

    isJust

    States if Maybe<A> is in just state.

    import { just } from 'fat-arrow-ts';  
      
    const myValue = just(5)  
      
    console.log(myValue.isJust) // true 
    isNone

    States if Maybe<A> is in none state.

    import { none } from 'fat-arrow-ts';  
      
    const myValue = none()
      
    console.log(myValue.isNone) // true 
    equals

    Takes an Maybe<any, any> in input and asserts if the passed value has the same state and structural equality.

    import { just, none } from 'fat-arrow-ts';  
      
    // With just()
    const aJustValue = just({ foo: 'foo' })
    console.log(aJustValue.equals(aJustValue)) // true
    
    const anotherJustValue = just({ bar: 'bar' })
    console.log(anotherJustValue.equals(aJustValue)) // false
    
    // Deep comparison
    console.log(aJustValue.equals(just({ foo: 'foo' }))) // true
    
    // With none()
    const aNoneValue = none()
    console.log(aNoneValue.equals(none())) // true
    console.log(aNoneValue === none()) // true
    fold

    It lets you handle or unwrap the raw value in your data type instances.

    It comes with two overloaded call signatures

    • () => void | A: will return the value as it is
    • (ifNone: () => B, isJust: (just: A) => B) => B: will accept two callbacks that will let you trigger side effects or map the value before returning it.
    import { right, left } from 'fat-arrow-ts';  
      
    const aJustValue = just(5)
    
    console.log(myValue.fold()) // 5
    
    // Mapping values
    const aNoneValue = none()
    
    console.log(aNoneValue.fold(() => 0, it => it)) // 0
    
    // Triggering side effects
    aNoneValue.fold(
      () => {
        // Only the 'none' callback will be applied to the value
        console.error('Nothing to see here!') // 'Nothing to see here!'
      }, 
      it => {
        console.log(it)
      }
    )
    flatMap

    Takes a callback of type <B>(value: A) => B | Maybe<B> and applies it to the just value of your type class instances.

    By default flatMap method will try to convert the returned value to an Maybe<B> just state so that you can also produce raw values from your callbacks.

    Returning a none value, you can switch to a none state.

    If you are used to ES Promises you may find similarities with the .then() method.

    import { just, none, Maybe } from 'fat-arrow-ts'; 
      
    const myValue = just(5)
     
    const justResult: Maybe<number> = myValue.flatMap(
      it => it + 5
    )
    
    console.log(justResult.isJust) // true 
    console.log(justResult.fold()) // 10
    
    // Will be flattened
    const sameTypeJustResult: Maybe<number> = myValue.flatMap(
      it => just(it + 5)
    )
    
    console.log(sameTypeJustResult.isJust) // true 
    console.log(sameTypeJustResult.fold()) // 10
    
    // You can return just values with a different type
    const anotherTypeJustResult: Maybe<string> = myValue.flatMap(
      it => just('foo')
    )
    
    console.log(anotherTypeJustResult.isJust) // true 
    console.log(anotherTypeJustResult.fold()) // 'foo'
    
    // You can return none values
    const noneResult: Maybe<number> = myValue.flatMap(
      it => none()
    )
    
    console.log(noneResult.isNone) // true 
    console.log(noneResult.fold()) // undefined
    mapIf

    Works very similar to flatMap but it also accepts a predicate (value: A) => boolean as first parameter.

    It will map your type class instances only if the predicate returns true.

    import { Maybe } from 'fat-arrow-ts';
    
    const getUserPersonalWebsiteFromInput = (): Maybe<string> => {
    	//...
    }
    
    const maybeInput = getUserPersonalWebsiteFromInput()
    
    // Only a non-nullable input will be trimmed
    const normalizedUrl = maybeInput.mapIf(
      it => it.endsWith('/'), it => it.slice(0, -1)
    )
    orElse

    It takes a callback of type () => A | Maybe<A> and lets you recover from a nullable value.

    If you are used to ES Promises you may find similarities with the .catch() method.

    import { Maybe } from 'fat-arrow-ts'; 
    
    const getUserInput = (): Maybe<string> => { 
      //...
    }
    
    const maybeInput = getUserInput()
    
    const recovered = maybeInput.orElse(() => 'N/A')
    
    console.log(recovered.isJust) // true
    console.log(recovered.fold()) // 'N/A'
    toEither

    TBD...

    Maybe Jest matchers

    See Setup Jest matchers for installation instructions.

    toBeJust

    Asserts if expected is just and has the expected value. It accepts both raw values and data type instances.

    import { just } from 'fat-arrow-ts'
     
    it('is just', () => {
        const actual = maybe(5) // or just(5)
    
        expect(actual).toBeJust(5);
    })
    toBeNone

    Asserts if expected is none.

    import { none } from 'fat-arrow-ts'
     
    it('is none', () => {
        const actual = none()
    
        expect(actual).toBeNone();
    })
    toHaveBeenLastCalledWithJust

    Asserts if a jest.Mock has been called last time with the expected just value

    it('is called with just', () => {
        const spy = jest.fn()
    
        runYourCode(spy)
    
        expect(spy).toHaveBeenLastCalledWithJust('expected just value');
    })
    toHaveBeenLastCalledWithNone

    Asserts if a jest.Mock has been called last time with a none value

    it('is called with none', () => {
        const spy = jest.fn()
    
        runYourCode(spy)
    
        expect(spy).toHaveBeenLastCalledWithNone();
    })

    Either

    An Either<E, A> value may contain either a value of type E or a value of type A, at any given time. In other words, it could be in left state or right state.

    right

    Takes a value in input and creates an Either<E, A> object with right state.

    import { right } from 'fat-arrow-ts';  
      
    const myValue = right<Error, number>(5)  
      
    console.log(myValue.fold()) // 5  
      
    // Flattening  
    console.log(left(myValue).isRight) // true  
    console.log(right(myValue).equals(myValue)) // true  

    left

    Takes a value in input and creates an Either<E, A> object with left state.

    import { left } from 'fat-arrow-ts';  
      
    const myValue = left<Error, number>(new Error('Ouch!'))  
      
    console.log(myValue.fold()) // Error  
      
    // Flattening  
    console.log(left(myValue).equals(myValue)) // true  
    console.log(right(myValue).isLeft) // true  

    Either API

    Let's go through all Either<E, A> properties and methods

    isRight

    States if Either<E, A> is in right state.

    import { right } from 'fat-arrow-ts';  
      
    const myValue = right<Error, number>(5)  
      
    console.log(myValue.isRight) // true 
    isLeft

    States if Either<E, A> is in left state.

    import { left } from 'fat-arrow-ts';  
      
    const myValue = left<Error, number>(new Error('Ouch!'))  
      
    console.log(myValue.isLeft) // true 
    equals

    Takes an Either<any, any> in input and asserts if the passed value has the same state and structural equality.

    import { right, left } from 'fat-arrow-ts';  
      
    const aRightValue = right<object, object>({ foo: 'foo' })
    console.log(aRightValue.equals(aRightValue)) // true
    
    const anotherRightValue = right<object, object>({ bar: 'bar' })
    console.log(anotherRightValue.equals(aRightValue)) // false
    
    const aLeftValueWithSameContents = left<object, object>({ foo: 'foo' })
    console.log(aLeftValueWithSameContents.equals(aRightValue)) // false
    
    // Deep comparison
    console.log(aRightValue.equals(right({ foo: 'foo' }))) // true
    fold

    It lets you handle or unwrap the raw value in your data type instances.

    It comes with two overloaded call signatures

    • () => E | A: will return the value as it is
    • (ifLeft: (left: E) => B, ifRight: (right: A) => B) => B: will accept two callbacks that will let you trigger side effects or map the value before returning it.
    import { right, left } from 'fat-arrow-ts';  
      
    const aRightValue = right<Error, number>(5)
    
    console.log(myValue.fold()) // 5
    
    // Mapping values
    const aLeftValue = left<Error, number>(new Error('Ouch!'))
    
    console.log(aLeftValue.fold(e => 0, it => it)) // 0
    
    // Triggering side effects
    aLeftValue.fold(
      e => {
        // Only the left callback will be applied to the value
        console.error(e) // Error
      }, 
      it => {
        console.log(it)
      }
    )
    flatMap

    Takes a callback of type (value: A) => B | Either<E, B> and applies it to the right value of your type class instances.

    By default flatMap method will try to convert the returned value to an Either<E, B> right state so that you can also produce raw values from your callback.

    Returning a left value, you can switch to a left state.

    If you are used to ES Promises you may find similarities with the .then() method.

    import { right, left } from 'fat-arrow-ts'; 
      
    const myValue = right<Error, number>(5)
     
    const rightResult: Either<Error, number> = myValue.flatMap(
      it => it + 5
    )
    
    console.log(rightResult.isRight) // true 
    console.log(rightResult.fold()) // 5
    
    // Will be flattened
    const sameTypeResult: Either<Error, number> = myValue.flatMap(
      it => right(it + 5)
    )
    
    console.log(sameTypeResult.isRight) // true 
    console.log(sameTypeResult.fold()) // 5
    
    // You can return right values with a different type
    const anotherTypeRightResult: Either<Error, string> = myValue.flatMap(
      it => right('foo')
    )
    
    console.log(anotherTypeRightResult.isRight) // true 
    console.log(anotherTypeRightResult.fold()) // 'foo'
    
    // Will be flattened to a Either<Error, number> with left state
    const leftResult: Either<Error, number> = myValue.flatMap(
      it => left<Error, number>(new Error())
    ) 
    
    console.log(leftResult.isLeft) // true 
    console.log(leftResult.fold()) // Error
    mapIf

    Works very similar to flatMap but it also accepts a predicate (value: A) => boolean as first parameter.

    It will map your type class instances only if the predicate returns true.

    import { right, left } from 'fat-arrow-ts'; 
    
    const isFizzBuzz = (it: number) => it % 15 === 0
    const isFizz = (it: number) => it % 3 === 0
    const isBuzz = (it: number) => it % 5 === 0
    
    const fizzBuzz = (i: number) =>
    	right<string, number>(i)
    		.mapIf(isFizzBuzz, () => left('FizzBuzz'))
    		.mapIf(isFizz, () => left('Fizz'))
    		.mapIf(isBuzz, () => left('Buzz'))
    		.fold()
    
    console.log(fizzBuzz(3)) // Fizz
    console.log(fizzBuzz(5)) // Buzz
    console.log(fizzBuzz(15)) // FizzBuzz
    console.log(fizzBuzz(2)) // 2
    mapLeft

    Similar to flatMap, it will let you apply callbacks of type (value: E) => G | Either<G, A> to the left value of your type class instances.

    By default mapLeft method will try to convert the returned value to an Either<G, A> left state so that you can also produce raw values from your callback.

    Returning a right value, you can switch your type class instances to a right state.

    import { right, left } from 'fat-arrow-ts';
      
    const myValue = left<Error, number>(new Error('Ouch!'))
     
    const leftResult: Either<Error, number> = myValue.mapLeft(
      it => new Error(`Error was ${it.message}`)
    )
    
    console.log(leftResult.isLeft) // true 
    console.log(leftResult.fold()) // Error
    
    // Will be flattened
    const sameTypeLeftResult: Either<Error, number> = myValue.mapLeft(
      it => left(new Error(`Error was ${it.message}`))
    )
    
    console.log(sameTypeLeftResult.isLeft) // true 
    console.log(sameTypeLeftResult.fold()) // Error
    
    // You can return left values with a different type
    const anotherTypeLeftResult: Either<string, number> = myValue.mapLeft(
      it => left('foo')
    )
    
    console.log(anotherTypeLeftResult.isLeft) // true 
    console.log(anotherTypeLeftResult.fold()) // 'foo'
    
    // Will be flattened to a Either<Error, number> with right state
    const rightResult: Either<Error, number> = myValue.mapLeft(
      it => right(5)
    )
    
    console.log(rightResult.isRight) // true 
    console.log(rightResult.fold()) // 5
    orElse

    It takes a callback of type (value: E) => A | Either<E, A> and applies it to the left value of your type class instances.

    The main difference with mapLeft is that it will try to convert the mapped value to a right state. A useful tool to recover from errors.

    If you are used to ES Promises you may find similarities with the .catch() method.

    import { Either } from 'fat-arrow-ts'; 
    
    const getUserInput = (): Either<Error, string> => {
      //...
    }
    
    const userInput = getUserInput()
    
    const recovered = userInput.orElse(e => {
      console.error(e) // Error
      return 'Who cares!'
    })
    
    console.log(recovered.isRight) // true
    console.log(recovered.fold()) // 'Who cares!'
    bimap

    TBD...

    toMaybe

    TBD...

    Either Jest matchers

    See Setup Jest matchers for installation instructions.

    toBeRight

    Asserts if expected is right and has the expected value. It accepts both raw values and data type instances.

    import { right } from 'fat-arrow-ts'
     
    it('is right', () => {
        const actual = right<Error, number>(5)
    
        expect(actual).toBeRight(5);
    })
    toBeLeft

    Asserts if expected is left and has the expected value. It accepts both raw values and data type instances.

    import { left } from 'fat-arrow-ts'
     
    it('is left', () => {
        const actual = left<Error, number>(new Error())
    
        expect(actual).toBeLeft(new Error());
    })
    toHaveBeenLastCalledWithRight

    Asserts if a jest.Mock has been called last time with the expected right value

    it('is called with right', () => {
        const spy = jest.fn()
    
        runYourCode(spy)
    
        expect(spy).toHaveBeenLastCalledWithRight('expected right value');
    })
    toHaveBeenLastCalledWithLeft

    Asserts if a jest.Mock has been called last time with the expected left value

    it('is called with left', () => {
        const spy = jest.fn()
    
        runYourCode(spy)
    
        expect(spy).toHaveBeenLastCalledWithLeft('expected left value');
    })

    Result

    A Result<A> is a type alias for Either<Error, A>.

    tryCatch

    It takes a callback () => A | Result<A> in input that will be run safely and returns a Result<A> instance.

    • if the callback runs correctly the result of the callback will be returned as a Result<A> with right state.
    • if the callback throws an error, the Error will be returned as a Result<A> with left state.
    import { tryCatch, Result } from 'fat-arrow-ts';
    
    const getFullName = (name: string, surname: string): string => {
        if (name.length < 1 || name.surname < 1) {
            throw new Error()
        }
        return `${name} ${surname}` 
    }
    
    //-- If callback runs correctly --//
    
    const result: Result<string> = tryCatch(() => getFullName('John', 'Doe'))
    
    const myValue = result.flatMap((it) => it.toUpperCase())
      
    console.log(myValue.fold()) // JOHN DOE
    console.log(myValue.isRight) // true
      
    //-- If callback throws --//
    
    const safeResult: Result<string> = tryCatch(() => getFullName('', ''))
    
    const mySafeValue = safeResult.flatMap((it) => it.toUpperCase())
      
    console.log(mySafeValue.fold()) // Error
    console.log(mySafeValue.isLeft) // true  

    Validation

    A Validation<E, A> is a type alias for Either<E[], A>.

    As you can see you can have many left values for a single right value. This kind of data type is useful to model validation outputs.

    validate

    It takes a value in input and an array of validation functions. It then runs through all the validation functions collecting all possible left values, if any.

    A validation function must return a pass value, containing A, otherwise a fail value containing E.

    If all the validations pass the validation output will have right state, otherwise a left state.

    import { fail, pass, validate, Validation } from 'fat-arrow-ts'
    
    const isPasswordLongEnough = (password: string): Validation<string, string> =>
    		password.length > 6 ? pass(password) : fail('Password must have more than 6 characters.')
    
    const isPasswordStrongEnough = (password: string): Validation<string, string> =>
    	/[\W]/.test(password) ? pass(password) : fail('Password must contain a special character.')
    
    const validations = [isPasswordLongEnough, isPasswordStrongEnough]
    
    const validatePassword = (pwd: string) => validate(pwd, validations)
    
    //-- If all validations pass --//
    console.log(validatePassword('qwertyu!').fold()) // 'qwertyu!'
    console.log(validatePassword('qwertyu!').isRight) // true
    
    //-- If one (or more) validations fail --//
    console.log(validatePassword('qwerty').fold()) // ['Password must have more than 6 characters.', 'Password must contain a special character.']
    console.log(validatePassword('qwerty').isLeft) // true

    pass

    Takes a value in input and creates a Validation<E, A> object with right state.

    import { pass, fail } from 'fat-arrow-ts';  
      
    const myValue = pass(5)  
      
    console.log(myValue.fold()) // 5  
    console.log(myValue.isRight) // true  
      
    // Flattening  
    console.log(pass(myValue).equals(myValue)) // true  
    console.log(fail(myValue).equals(myValue)) // true

    fail

    Takes a value in input and creates a Validation<E, A> object with left state.

    The input could be a plain E value or an array E[].

    import { pass, fail } from 'fat-arrow-ts';  
      
    const myValue = fail('error')
      
    console.log(myValue.fold()) // ['error']
    console.log(myValue.isLeft) // true
    
    // Using arrays
    const anotherValue = fail(['first error', 'second error'])
    
    console.log(myValue.fold()) // ['first error', 'second error']
    console.log(myValue.isLeft) // true
      
    // Flattening  
    console.log(pass(myValue).equals(myValue)) // true  
    console.log(fail(myValue).equals(myValue)) // true  

    Examples

    Install

    npm i fat-arrow-ts

    DownloadsWeekly Downloads

    1

    Version

    0.9.0

    License

    MIT

    Unpacked Size

    60.9 kB

    Total Files

    42

    Last publish

    Collaborators

    • avatar