Nondeterministic Polynomial Munchies
    Have ideas to improve npm?Join in the discussion! »

    result.ts
    TypeScript icon, indicating that this package has built-in type declarations

    0.3.1 • Public • Published

    Result

    travis npm downloads docs

    Library for representing the Result of a computation that may fail. Which is a type friendly alternative to handling errors than exceptions are.

    Usage

    A Result<x, a> is either Ok<a> meaning the computation succeeded with a value, or it is an Error<x> meaning that there was some x failure.

    type Result <x, a> = 
      | { isOk:true, value: a }
      | { isOk:false, error:x }

    Actual Result<x, a> interface is more complex as it provides all of the library functions as methods as well, but type signature above is a good summary.

    Import

    All the examples above assume following import:

    import * as Result from 'result.ts'

    Construct results

    Result.ok(value:a) => Result<x, a>

    Funciton ok constructs result of successful computaion:

    Result.ok(5) // => {isOk:true, value:5}

    Result.error(error:x) => Result<x, a>

    Function error constructs a failed computation:

    Result.ok('Oops!') // => {isOk:false, error:'Oops!'}

    Result.fromMaybe(error:x, value:void|null|undefined|a):Result<x, a>

    Convert from a Maybe<a> (which is void|null|undefined|a) to a result.

    const parseInt = (input:string):null|number => {
      const value = Number.parseInt(input)
      if (Number.isNaN(value)) {
        return null
      } else {
        return value
      }
    }
     
    const readInt = (input:string):Result<string, number> =>
      Result.fromMaybe(`Input: "${input}" can not be read as an Int`,
                        parseInt(input))
     
    readInt('5') // => Result.ok(5)
    readInt('a') // => Result.error('Input: "a" can not be read as an Int')

    P.S.: In the further examples we will make use of above defined readInt.

    Unbox results

    result.isOk:boolean

    You can use isOk:boolean common member to differentiate between Ok<a> and Error<x> possible instances of Result<x,a> and access to the corresponding properties:

    const result = readInt(data)
    if (result.isOk) {
      console.log(result.value + 15)
    } else {
      console.error(result.error)
    }

    Result.isOk(result:Result<x, a>):boolean

    Library also provides type predicate as an alternative way to narrow down the Result<x, a> to Ok or Error:

    const result = readInt(data)
    if (Result.isOk(result)) {
      console.log(result.value + 15)
    } else {
      console.error(result.error)
    }

    Result.isError(result:Result<x, a>):boolean

    For the symetry library also provides Result.isError type predicate:

    const result = readInt(data)
    if (Result.isError(resut)) {
      console.error(result.error)
    } else {
      console.log(result.value + 15)
    }

    <Result<x, a>>resut.toValue(fallback:a):a

    It is also possible unbox Result<x, a> by providing a fallback:a value in case result is a failed computation.

    readInt("123").toValue(0) // => 123
    readInt("abc").toValue(0) // => 0

    If the result is Ok<a> it returns the value, but if the result is an Error return a given fallback value.

    Result.toValue(result:Result<x, a>, fallback:a>):a

    Same API is also available as a function:

    Result.toValue(readInt("123"), 0) // => 123
    Result.toValue(readInt("abc"), 0) // => 0

    <Result<x, a>>result.toMaybe():null|a

    If actual error is not needed it is also possible to covert Result<x, a> to Maybe<a> (More specifically undefined|null|void|a):

    readInt("123").toMaybe() // => 123
    readInt("abc").toMaybe() // => null

    Result.toMaybe(result:Result<x, a>):null|a

    Same API is also available as a funciton:

    Result.toMaybe(readInt("123")) // => 123
    Result.toMaybe(readInt("abc")) // => null

    Transform results

    <Result<x, a>>result.map(f:(value:a) => b):Result<x, b>

    Applies a function to a Result<x, a>. If the result is Ok underlaying value will be mapped. If the result is an Error, the same error value will propagate through.

    Result.ok(3).map(x => x + 1) // => Result.ok(4)
    Result.error('bad input').map(x => x + 1) // => Result.error('bad input')

    Result.map(f:(value:a) => a, result:Result<x, a>):Result<x, a>

    Same API is also available as a function:

    Result.map(x => x + 1, Result.ok(3)) // => ok(4)
    Result.map(x => x + 1, Result.error('bad input')) // => error('bad input')

    <Result<x,a>>result.format(f:(error:x) => y):Result<y, a>

    It is also possible to map an error of the result. For example, say the errors we get have too much information:

    Result
      .error({ reason:'Bad input', filename: '/path' })
      .format(error => error.reason) // => Result.error('Bad input')
     
    Result
      .ok(4)
      .format(error => error.reason) // => Result.ok(4)

    Result.format(f:(error:x) => y, result:Result<x, a>):Result<y, a>

    Same API is also avaiable as a funciton:

    Result.format(error => error.reason,
                  Result.error({ reason:'Bad input', filename: '/path' }))
    // => Result.error('Bad input')
     
    Result.format(error => error.reason, Result.ok(4)) // => Result.ok(4)

    <Result<x, a>>result.format(f:(error:x) => a):Result<x, a>

    It is also possible to transform failed Result<x, a> to succeeded result by mapping x to a:

    Result.error('Bad input').recover(Error) // => Result.ok(Error('Bad input'))

    Result.recover((error:x) => a, result:Result<x, a>):Result<x, a>

    Same API is also available as a function:

    Result.recover(Error,
                    Result.error('Bad Input')) // => Result.ok(Error('Bad Input'))

    Chaining results

    <Result<x, a>>result.chain(next:(value:a) => Result<x, b>):Result<x, b>

    It is possible to chain a sequence of computations that may fail:

    type Month = 1|2|3|4|5|6|7|8|9|10|11|12
    const toValidMonth = (n:number):Result<string, Month> => {
      if (n >= 1 && n <= 12 && Math.floor(n) === n) {
          return Result.ok(<Month>n)
      } else {
        return Result.error(`Number: ${n} is not with-in 0 to 12 month range`)
      }
    }
     
    const parseMonth = (input:string):Result<string, Month> =>
      readInt(input).chain(toValidMonth)
     
    parseMonth('4') // => Result.ok(4)
    parseMonth('a') // => Result.error('Input: "a" can not be read as an Int')
    parseMonth('13') // => Result.error('Number 13 is not with-in 0 to 12 month range') 

    Result.chain(f:(value:a) => Result<x, b>, r:Result<x, a>):Result<x, b>

    Same API is also available as a function:

    const parseMonth = (input:string):Result<string, Month> =>
      Result.chain(toValidMonth, readInt(input))
     
    parseMonth('7') // => Result.ok(7)
    parseMonth('Hi') // => Result.error('Input: "Hi" can not be read as an Int')
    parseMonth('0') // => Result.error('Number: 0 is not with-in 0 to 12 month range')

    <Result<x, a>>result.and(other:Result<x, b>):Result<x, b>

    Sometimes you want to chain a sequence of computations, but unlike in previous example, result of next computation does not depend on result of previous one:

    Result.ok(2).and(Result.error('late error')) // => Result.error('late error')
    Result.error('early error').and(Result.ok(1)) // => Result.error('early error')
     
    Result.error('early').and(Result.error('late')) // => Result.error('early')
    Result.ok(2)
          .and(Result.ok('diff result type')) // => Result.ok('diff result type)

    Result.and(left:Result<x, a>, right:Result<x, b>):Result<x, b>

    Same API is available through a function as well:

    {
    const {ok, error} = Result
    Result.and(ok(2), error('late error')) // => Result.error('late error')
    Result.and(error('early error'), ok(1)) // => Result.error('early error')
     
    Result.and(error('early'), error('late')) // => Result.error('early')
    Result.and(ok(2), ok('diff result type')) // => Result.ok('diff result type)
    }

    <Result<x, a>>result.capture(f:(error:x) => Result<y, a>):Result<y, a>

    It is also possible to chain a sequence of computations that may fail, such that next computation is performed when previous one fails:

    const fromMonthName = (month:string):Month|null => {
      switch (month.toLowerCase()) {
        case "january": return 1
        case "february": return 2
        case "march": return 3
        case "april": return 4
        case "may": return 5
        case "june": return 6
        case "july": return 7
        case "august": return 8
        case "september": return 9
        case "october": return 10
        case "november": return 11
        case "december": return 12
        default: return null
      }
    }
     
    const readMonthByName = (input: string):Result<string, Month> =>
      Result.fromMaybe(`Input "${input}" is not a valid month name`,
                        fromMonthName(input))
     
     
    const readMonth = (input:string):Result.Result<string, Month> =>
      parseMonth(input)
      .capture(intError =>
                readMonthByName(input)
                .format(nameError => `${intError} or ${nameError}`))
     
    readMonth('3') // => Result.ok(3)
    readMonth('June') // => Result.ok(6)
    readMonth('17') // => Result.error('Input: 17 is not with-in 0 to 12 month range & Input "17" is not a valid month name')
    readMonth('Jude') // Result.error('Input: "Jude" can not be read as an Int & Input: "Jude" is not a valid month name')

    Result.capture(f:(error:x) => Result<y, a>, r:Result<x, a>):Result<y, a>

    Same API is also available via function:

    const readMonth = (input:string):Result.Result<string, Month> =>
      Result.caputer(badInt =>
                        readMonthByName(input).
                          format(badName => `${badInt} or ${badName}`),
                      parseMonth(input))
     
    readMonth('3') // => Result.ok(3)
    readMonth('June') // => Result.ok(6)
    readMonth('17') // => Result.error('Input: 17 is not with-in 0 to 12 month range & Input "17" is not a valid month name')
    readMonth('Jude') // Result.error('Input: "Jude" can not be read as an Int & Input: "Jude" is not a valid month name')

    <Result<x, a>>result.or(other:Result<y, a>):Result<y, a>

    It is also possible to chain a fallback computation that is performed if original fails, but unlike example above ignoring the first error:

    {
    const {ok, error} = Result
     
    ok(2).or(error('late error')) // => Result.ok(2)
    error('early error').or(ok(3)) // => Result.ok(3)
    error(-1).or(error('diff result type'})) // => Result.error('diff result type')
    ok(2).or(ok(100)) // => Result.ok(2)
    }

    Result.or(left:Result<x, a>, right:Result<y, a>):Result<y, a>

    As in all other cases same API is availabel via function as well:

    {
    const {ok, error} = Result
     
    Result.or(ok(2), error('late error')) // => Result.ok(2)
    Result.or(error('early error'), ok(3)) // => Result.ok(3)
    Result.or(error(-1), error('diff result type'})) // => Result.error('diff result type')
    Result.or(ok(2), ok(100)) // => Result.ok(2)
    }

    Prior Art

    This library is inspired by:

    Keywords

    Install

    npm i result.ts

    DownloadsWeekly Downloads

    7

    Version

    0.3.1

    License

    MIT

    Last publish

    Collaborators

    • avatar