Result
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.
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:
Construct results
Result.ok(value:a) => Result<x, a>
Funciton ok
constructs result of successful computaion:
Result.ok5 // => {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.
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:
if result.isOk else
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
:
if Result.isOkresult else
Result.isError(result:Result<x, a>):boolean
For the symetry library also provides Result.isError
type predicate:
if Result.isErrorresut else
<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".toValue0 // => 123readInt"abc".toValue0 // => 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.toValuereadInt"123", 0 // => 123Result.toValuereadInt"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 // => 123readInt"abc".toMaybe // => null
Result.toMaybe(result:Result<x, a>):null|a
Same API is also available as a funciton:
Result.toMaybereadInt"123" // => 123Result.toMaybereadInt"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.ok3.mapx + 1 // => Result.ok(4)Result.error'bad input'.mapx + 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.mapx + 1, Result.ok3 // => ok(4)Result.mapx + 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 .formaterror.reason // => Result.error('Bad input') Result .ok4 .formaterror.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.formaterror.reason, Result.error// => Result.error('Bad input') Result.formaterror.reason, Result.ok4 // => 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'.recoverError // => 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.recoverError, 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:
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:
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.ok2.andResult.error'late error' // => Result.error('late error')Result.error'early error'.andResult.ok1 // => Result.error('early error') Result.error'early'.andResult.error'late' // => Result.error('early')Result.ok2 .andResult.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:
<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:
.capturereadMonthByNameinput .format` or ` 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:
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:
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:
Prior Art
This library is inspired by: