0.3.1 • Published 7 years ago

result.ts v0.3.1

Weekly downloads
15
License
MIT
Repository
github
Last release
7 years ago

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:

0.3.1

7 years ago

0.3.0

7 years ago

0.2.0

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.1.0

7 years ago