diff --git a/docs/source/en/data/maybe/index.md b/docs/source/en/data/maybe/index.md index f10b158..582b04b 100644 --- a/docs/source/en/data/maybe/index.md +++ b/docs/source/en/data/maybe/index.md @@ -68,7 +68,7 @@ If we have maybe, we can change our code from:: find1([1, 2, 3], (x) => x > 2); // ==> 3 find1([1, 2, 3], (x) => x > 3); // ==> null -To: +To:: const Maybe = require('folktale/data/maybe'); @@ -128,7 +128,7 @@ We'll see each of these categories in more details below. If we're wrapping a value in a Maybe, then we can use the value by extracting it from that container. Folktale lets you do this through the `getOrElse(default)` -method: +method:: const Maybe = require('folktale/data/maybe'); @@ -161,7 +161,7 @@ UI elements with the data from that object, but the list can be empty so you have to handle that error first. We can't use `getOrElse()` here because if we have an error, we don't want to render that error in the same way. Instead, we can use `map(transformation)` to apply our rendering logic only to successful -values: +values:: const Maybe = require('folktale/data/maybe'); @@ -175,7 +175,7 @@ values: } first([{ title: 'Hello' }]).map(render); - // ==> Maybe.Just(['item', ['title', 'Hello']]) + // => Maybe.Just(['item', ['title', 'Hello']]) first([]).map(render); // ==> Maybe.Nothing() diff --git a/docs/source/en/data/result/index.md b/docs/source/en/data/result/index.md index ff9e9cd..b3b956a 100644 --- a/docs/source/en/data/result/index.md +++ b/docs/source/en/data/result/index.md @@ -47,37 +47,77 @@ In JavaScript you're often left with two major ways of dealing with these failures: a branching instruction (like `if/else`), or throwing errors and catching them. +To see how `Result` compares to these, we'll look at a function that needs to +validate some information, and that incorporates some more complex validation +rules. A person may sign-up for a service by providing the form they would +prefer being contacted (email or phone), and the information related to that +preference has to be provided, but any other info is optional:: + + // Functions to assert the format of each data + const isValidName = (name) => name.trim() !== ''; + const isValidEmail = (email) => /(.+)@(.+)/.test(email); + const isValidPhone = (phone) => /^\d+$/.test(phone); + + // Objects representing each possible failure in the validation + const { data, setoid } = require('folktale/core/adt'); + + const ValidationErrors = data('validation-errors', { + Required(field) { + return { field }; + }, + + InvalidEmail(email) { + return { email }; + }, + + InvalidPhone(phone) { + return { phone }; + }, + + InvalidType(type) { + return { type }; + }, + + Optional(error) { + return { error }; + } + }).derive(setoid); + + const { + Required, + InvalidEmail, + InvalidPhone, + InvalidType, + Optional + } = ValidationErrors; + Branching stops being a very feasible thing after a couple of cases. It's very simple to forget to handle failures (often with catastrophic effects, as can be seen in things like NullPointerException and the likes), and there's no error propagation, so every part of the code has to handle the same error over and over again:: - const isValidEmail = (email) => /(.+)@(.+)/.test(email); - - const isValidPhone = (phone) => /^\d+$/.test(phone); - const validateBranching = ({ name, type, email, phone }) => { - if (name.trim() === '') { - return '`name` can’t be empty.'; + if (!isValidName(name)) { + return Required('name'); } else if (type === 'email') { if (!isValidEmail(email)) { - return 'Please provide a valid email'; + return InvalidEmail(email); } else if (phone && !isValidPhone(phone)) { - return 'The phone number is not valid'; + return Optional(InvalidPhone(phone)); } else { return { type, name, email, phone }; } } else if (type === 'phone') { if (!isValidPhone(phone)) { - return 'Please provide a valid phone number'; + return InvalidPhone(phone); } else if (email && !isValidEmail(email)) { - return 'The email is not valid'; + return Optional(InvalidEmail(email)); } else { return { type, name, email, phone }; } } else { - return 'The type should be either `phone` or `email`'; + return InvalidType(type); } }; @@ -87,7 +127,7 @@ over again:: type: 'email', phone: '11234456' }); - // ==> 'Please provide a valid email' + // ==> InvalidEmail(undefined) validateBranching({ name: 'Alissa', @@ -103,35 +143,37 @@ often results in crashing the process, which is better than continuing but doing the wrong thing—, but they allow failures to propagate, so fewer places in the code need to really deal with the problem:: - const assertEmail = (email) => { + const id = (a) => a; + + const assertEmail = (email, wrapper=id) => { if (!isValidEmail(email)) { - throw new Error('Please provide a valid email'); + throw wrapper(InvalidEmail(email)); } }; - const assertPhone = (phone) => { + const assertPhone = (phone, wrapper=id) => { if (!isValidPhone(phone)) { - throw new Error('Please provie a valid phone number'); + throw wrapper(InvalidEmail(email)); } }; const validateThrow = ({ name, type, email, phone }) => { - if (name.trim() === '') { - throw new Error('`name` can’t be empty'); + if (!isValidName(name)) { + throw Required('name'); } switch (type) { case 'email': assertEmail(email); - if (phone) assertPhone(phone); + if (phone) assertPhone(phone, Optional); return { type, name, email, phone }; case 'phone': assertPhone(phone); - if (email) assertEmail(email); + if (email) assertEmail(email, Optional); return { type, name, email, phone }; default: - throw new Error('The type should be either `phone` or `email`'); + throw InvalidType(type); } }; @@ -143,7 +185,7 @@ code need to really deal with the problem:: phone: '11234456' }); } catch (e) { - e.message; // ==> 'Please provide a valid email' + e; // ==> InvalidEmail(undefined) } validateThrow({ @@ -161,11 +203,730 @@ recover from a failure when you don't know in which state your application is? `Result` helps with both of these cases. With a `Result`, the user is forced to be aware of the failure, since they're not able to use the value at all without -unwrapping the value before. At the same time, using a `Result` value will +unwrapping the value first. At the same time, using a `Result` value will automatically propagate the errors when they're not handled, making error handling easier. Since `Result` runs one operation at a time when you use the value, and does not do any dynamic stack unwinding (as `throw` does), it's much easier to understand in which state your application should be. +Using `Result`, the previous examples would look like this:: + + const Result = require('folktale/data/result'); + + const checkName = (name) => + isValidName(name) ? Result.Ok(name) + : /* otherwise */ Result.Error(Required('name')); + + const checkEmail = (email) => + isValidEmail(email) ? Result.Ok(email) + : /* otherwise */ Result.Error(InvalidEmail(email)); + + const checkPhone = (phone) => + isValidPhone(phone) ? Result.Ok(phone) + : /* otherwise */ Result.Error(InvalidPhone(phone)); + + const optional = (check) => (value) => + value ? check(value).mapError(Optional) + : /* otherwise */ Result.Ok(value); + + const maybeCheckEmail = optional(checkEmail); + const maybeCheckPhone = optional(checkPhone); + + + const validateResult = ({ name, type, email, phone }) => + checkName(name).chain(_ => + type === 'email' ? checkEmail(email).chain(_ => + maybeCheckPhone(phone).map(_ => ({ + name, type, email, phone + })) + ) + + : type === 'phone' ? checkPhone(phone).chain(_ => + maybeCheckEmail(email).map(_ => ({ + name, type, email, phone + })) + ) + + : /* otherwise */ Result.Error(InvalidType(type)) + ); + + + validateResult({ + name: 'Max', + type: 'email', + phone: '11234456' + }); + // ==> Result.Error(InvalidEmail(undefined)) + + validateResult({ + name: 'Alissa', + type: 'email', + email: 'alissa@somedomain' + }); + // => Result.Ok({ name: 'Alissa', type: 'email', phone: undefined, email: 'alissa@somedomain' }) + + +So, Results give you simpler and more predictable forms of error handling, that +still allow error propagation and composition to happen, as with regular +JavaScript exceptions, at the cost of some additional syntactical overhead, +since JavaScript does not allow one to abstract over syntax. + + +## Working with Result values + +A Result value may be one of the following cases: + + - `Ok(value)` — represents a successful value (e.g.: the correct return value + from a function). + + - `Error(value)` — represents an unsuccessful value (e.g.: an error during the + evaluation of a function). + +Functions that may fail just return one of these two cases instead of failing. +That is, instead of writing something like this:: + + //: (Number, Number) => Number throws DivisionByZero + const divideBy1 = (dividend, divisor) => { + if (divisor === 0) { + throw new Error('Division by zero'); + } else { + return dividend / divisor; + } + } + + divideBy1(6, 3); // ==> 2 + +One would write something like this:: + + const Result = require('folktale/data/result'); + + //: (Number, Number) => Result DivisionByZero Number + const divideBy2 = (dividend, divisor) => { + if (divisor === 0) { + return Result.Error('Division by zero'); + } else { + return Result.Ok(dividend / divisor); + } + } + + divideBy2(6, 3); // ==> Result.Ok(2) + +The function returns a value of the type `Result `, +which is quite similar to the first version of the function, with the difference +that the error is made into a real value that the function returns, and as such +can be interacted with in the same way one interacts with any other object in +the language. + +Because the value is wrapped in the `Result` structure, in order to use the +value one must first unwrap it. Folktale's `Result` provides methods to help +with this, and they can be divided roughly into 3 categories: + + - **Sequencing computations**: A `Result` is the result of a computation that + can fail. Sometimes we want to run several computations that may fail in + sequence, and these methods help with that. This is roughly the equivalent + of `;` in imperative programming, where the next instruction is only + executed if the previous instruction succeeds. + + - **Transforming values**: Sometimes we get a `Result` value that isn't + *quite* the value we're looking for. We don't really want to change the + status of the computation (errors should continue to be errors, successes + should continue to be successes), but we'd like to tweak the resulting + *value* a bit. This is the equivalent of applying functions in an + expression. + + - **Extracting values**: Sometimes we need to pass the value into things that + don't really know what a `Result` is, so we have to somehow extract the + value out of the structure. These methods help with that. + +We'll see each of these categories in more details below. + + +### Sequencing computations + +Because `Result` is used for partial functions which may fail in, possibly, many +different ways a common use case for the structure is combining all of these +functions such that only successful values flow through. + +You can sequence computations in this manner with the `Result` structure by +using the `.chain` method. The method takes as argument a single function that +will receive the value in the `Result` structure, and must return a new `Result` +structure, which then becomes the result of the method. Only successful values +flow through the function argument, errors are just propagated to the result +immediately. + +As an example, imagine we want to parse an integer. Integers are basically a +sequence of many digits, but we can start smaller and try figuring out how we +parse a single digit:: + + const Result = require('folktale/data/result'); + + const isDigit = (character) => + '0123456789'.split('').includes(character); + + const digit = (input) => { + const character = input.slice(0, 1); + const rest = input.slice(1); + + return isDigit(character) ? Result.Ok([character, rest]) + : /* otherwise */ Result.Error(`Expected a digit (0..9), got "${character}"`); + }; + + digit('012'); + // => Result.Ok(['0', '12']) + + digit('a12'); + // ==> Result.Error('Expected a digit (0..9), got "a"') + +So far our parser correctly recognises the first digit in a sequence of +characters, but to parse integers we need it to recognise more than one digit. +We can also only proceed to the next character if the first one succeeds +(otherwise we already know it's not an integer!). + +If we have a fixed number of digits that would look like the following:: + + const twoDigits = (input) => + digit(input).chain(([char1, rest]) => + digit(rest).chain(([char2, rest]) => + [char1 + char2, rest] + ) + ); + + twoDigits('012'); + // => Result.Ok(['01', '2']) + + twoDigits('a12'); + // ==> Result.Error('Expected a digit (0..9), got "a"') + + twoDigits('0a2'); + // ==> Result.Error('Expected a digit (0..9), got "a"') + +We can generalise this to arbitrary numbers of digits by writing a recursive +function:: + + const digits = (input) => + input === '' ? Result.Error('Expected a digit (0..9), but there was nothing to parse') + : /* otherwise */ digit(input).chain(([character, rest]) => + rest === '' ? Result.Ok(character) + : /* else */ digits(rest).chain(characters => + Result.Ok(character + characters) + ) + ); + + digits('012'); + // ==> Result.Ok('012') + + digits('a12'); + // ==> Result.Error('Expected a digit (0..9), got "a"') + + digits('01a'); + // ==> Result.Error('Expected a digit (0..9), got "a"') + + digits(''); + // ==> Result.Error('Expected a digit (0..9), but there was nothing to parse') + +> **NOTE** +> While the recursive `.chain` can be used when performing an arbitrary number +> of computations, it should be noted that it may grow the stack of calls in a +> JavaScript implementation beyond what that implementation supports, resulting +> in a `Max Call Stack Size Exceeded` error. + +Finally, parsing should give us a number instead of a string, so that string +needs to be converted. Since we already ensured that the resulting string only +has digits, the conversion from String to Number can never fail, but we can +still use `.chain` if we always return a `Result.Ok`:: + + const integer = (input) => + digits(input).chain(x => Result.Ok(Number(x))); + + integer('012'); + // ==> Result.Ok(12) + + integer('01a') + // ==> Result.Error('Expected a digit (0..9), got "a"') + + +### Transforming values + +Sometimes we want to transform just the value that is inside of a `Result`, +without touching the context of that value (whether the `Result` is a success or +an error). In the previous section, the `integer` transformation is a good +example of this. For these cases, the `.map` method can be used instead of the +`.chain` method:: + + const Result = require('folktale/data/result'); + + Result.Ok('012').map(Number); + // ==> Result.Ok(12) + +Note that, like `.chain`, the `.map` method only runs on successful values, thus +it propagates failures as well:: + + Result.Error('012').map(Number); + // ==> Result.Error('012') + + +### Extracting values + +While one can always just put all the rest of a computation inside of a +`.chain`, sometimes it may be preferrable to extract the value out of a `Result` +structure instead. For these cases there's a `.getOrElse` method:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).getOrElse('not found'); + // ==> 1 + + Result.Error(1).getOrElse('not found'); + // ==> 'not found' + +Additionally, if one doesn't care about whether the value failed or succeeded, +the `.merge` method just returns whatever value is wrapped in the `Result`:: + + Result.Ok(1).merge(); + // ==> 1 + + Result.Error(1).merge(); + // ==> 1 + + +## Error handling + +Since the purpose of a `Result` structure is to represent failures, we need to +be able to handle these failures in some way. The `.getOrElse` method gives us +some very specific and limited form of error handling, but if we want to *do* +something in order to recover from an error, this doesn't help us much. + +For a more general form of error handling, the `Result` structure provides the +`.orElse` method. This works pretty much in the same way `.chain` does, except +it invokes the function on errors (successes are propagated):: + + + const Result = require('folktale/data/result'); + + const parseNumber = (input) => + isNaN(input) ? Result.Error(`Not a number: ${input}`) + : /* otherwise */ Result.Ok(Number(input)); + + const parseBool = (input) => + input === 'true' ? Result.Ok(true) + : input === 'false' ? Result.Ok(false) + : /* otherwise */ Result.Error(`Not a boolean: ${input}`); + + + const parseNumberOrBool = (input) => + parseNumber(input) + .orElse(_ => parseBool(input)); + + + parseNumberOrBool('13'); + // ==> Result.Ok(13) + + parseNumberOrBool('true'); + // ==> Result.Ok(true) + + parseNumberOrBool('foo'); + // ==> Result.Error('Not a boolean: foo') + +As with successes' `.map`, one may also transform the failure value of a +`Result` without changing the context of the computation by using the +`.mapError` method:: + + const parseNumberOrBool2 = (input) => + parseNumber(input) + .orElse(_ => parseBool(input)) + .mapError(_ => `Not a number or boolean: ${input}`); + + parseNumberOrBool2('foo'); + // ==> Result.Error('Not a number or boolean: foo') + + +## Pattern matching + +As with other union structures in Folktale, Result provides a `.matchWith()` +method to perform a limited form of *pattern matching*. Pattern matching allows +one to specify a piece of code for each case in a structure, like an `if/else` +or `switch`, but specific to that structure. + +We could use `.matchWith()` to run different computations depending on whether a +MResult value represents a success or a failure, for example, without the +requirement of having to return a Result:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).matchWith({ + Ok: ({ value }) => `Ok: ${value}`, + Error: ({ value }) => `Error: ${value}` + }); + // ==> 'Ok: 1' + + Result.Error(1).matchWith({ + Ok: ({ value }) => `Ok: ${value}`, + Error: ({ value }) => `Error: ${value}` + }); + // ==> 'Error: 1' + + +## How does Result compare to Validation? + +Result and Validation are pretty close structures. They both try to represent +whether a particular thing has failed or succeeded, and even their vocabulary is +very similar (`Error` vs. `Failure`, `Ok` vs. `Success`). The major difference +is in some of their methods. + +A Result is a data structure that implements the Monad interface (`.chain`). +This makes Result a pretty good structure to model a sequence of computations +that may fail, where a computation may only run if the previous computation +succeeded. In this sense, a Result's `.chain` method is very similar to +JavaScript's `;` at the end of statements: the statement at the right of the +semicolon only runs if the statement at the left did not throw an error. + +A Validation is a data structure that implements the Applicative interface +(`.apply`), and does so in a way that if a failure is applied to another +failure, then it results in a new validation that contains the failures of both +validations. In other words, Validation is a data structure made for errors that +can be aggregated, and it makes sense in the contexts of things like form +validations, where you want to display to the user all of the fields that failed +the validation rather than just stopping at the first failure. + +Validations can't be as easily used for sequencing operations because the +`.apply` method takes two validations, so the operations that create them must +have been executed already. While it is possible to use Validations in a +sequential manner, it's better to leave the job to Result, a data structure made +for that. + + + +@annotate: folktale.data.result.Error +--- + +Constructs a Result whose value represents a failure. + +See the documentation for the Result structure to understand how to use this. + + +@annotate: folktale.data.result.Ok +--- + +Constructs a Result whose value represents a success. + +See the documentation for the Result structure to understand how to use this. + + +@annotate-multi: [folktale.data.result.Error.prototype.map, folktale.data.result.Ok.prototype.map] +--- + +Transforms the value inside of a Result structure with an unary function without +changing the context of the computation. That is, `Error` values continue to be +`Error` values, and `Ok` values continue to be `Ok` values. + +## Example:: + + const Result = require('folktale/data/result'); + + function increment(value) { + return value + 1; + } + + Result.Ok(1).map(increment); + // ==> Result.Ok(2) + + Result.Error(1).map(increment); + // ==> Result.Error(2) + + +@annotate-multi: [folktale.data.result.Error.prototype.apply, folktale.data.result.Ok.prototype.apply] +--- + +Applies the function contained in one Result to the value in another Result. +Application only occurs if both Results are `Ok`, otherwise keeps the first +`Error`. + +Note that `Result.Ok(x => x + 1).apply(Result.Ok(1))` is equivalent to +`Result.Ok(1).map(x => x + 1)`. + + +## Example:: + + const Result = require('folktale/data/result'); + + function increment(value) { + return value + 1; + } + + Result.Ok(increment).apply(Result.Ok(1)); + // ==> Result.Ok(2) + + Result.Ok(increment).apply(Result.Error(1)); + // ==> Result.Error(1) + + Result.Error(increment).apply(Result.Ok(1)); + // ==> Result.Error(increment) + + Result.Error(increment).apply(Result.Error(1)); + // ==> Result.Error(increment) + + +@annotate-multi: [folktale.data.result.Error.prototype.chain, folktale.data.result.Ok.prototype.chain] +--- + +Transforms the value and context of a Result computation with an unary function. +As with `.map()`, the transformation is only applied if the value is an `Ok`, +but the transformation is expected a new `Result` value, which then becomes the +result of the method. + + +## Example:: + + const Result = require('folktale/data/result'); + + const divideBy = (a) => (b) => + b === 0 ? Result.Error('division by zero') + : /* otherwise */ Result.Ok(a / b); + + + Result.Ok(4).chain(divideBy(2)); + // ==> Result.Ok(2) + + Result.Error(4).chain(divideBy(2)); + // ==> Result.Error(4) + + Result.Ok(4).chain(divideBy(0)); + // ==> Result.Error('division by zero') + + + +@annotate: folktale.data.result.get +deprecated: + version: '2.0.0' + replacedBy: unsafeGet() + reason: | + We want to discourage the use of partial functions, and having short names + makes it easy for people to want to use them without thinking about the + problems. + + For more details see https://github.com/origamitower/folktale/issues/42 +--- + +This method has been renamed to `unsafeGet()`. + + +@annotate-multi: [folktale.data.result.Error.prototype.unsafeGet, folktale.data.result.Ok.prototype.unsafeGet] +--- + +Extracts the value from a `Result` structure. + +> **WARNING** +> This method is partial, which means that it will only work for `Ok` +> structures, not for `Error` structures. It's recommended to use `.getOrElse()` +> or `.matchWith()` instead. + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).unsafeGet(); + // ==> 1 + + + try { + Result.Error(1).unsafeGet(); + // TypeError: Can't extract the value of an Error + } catch (e) { + e instanceof TypeError; // ==> true + } + + +@annotate-multi: [folktale.data.result.Error.prototype.getOrElse, folktale.data.result.Ok.prototype.getOrElse] +--- + +Extracts the value of a `Result` structure, if it exists (i.e.: is an `Ok`), +otherwise returns the provided default value. + + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).getOrElse(2); + // ==> 1 + + Result.Error(1).getOrElse(2); + // ==> 2 + + +@annotate-multi: [folktale.data.result.Error.prototype.orElse, folktale.data.result.Ok.prototype.orElse] +--- + +Allows recovering from `Error` values with a handler function. + +While `.chain()` allows one to sequence operations, such that the second +operation is ran only if the first one succeeds, the `.orElse()` method allows +one to recover from an `Error` by running a function that provides a new Result +value. + + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Ok(4).orElse(error => Result.Ok(error + 1)); + // ==> Result.Ok(2) + + Result.Error(4).orElse(error => Result.Ok(error + 1)); + // ==> Result.Ok(5) + + Result.Error(4).orElse(error => Result.Error(error - 1)); + // ==> Result.Error(3) + + +@annotate-multi: [folktale.data.result.Error.prototype.fold, folktale.data.result.Ok.prototype.fold] +--- + +Applies a function to each case of a Result. + +## Example:: + + const Result = require('folktale/data/result'); + + const inc = (x) => x + 1; + const dec = (x) => x - 1; + + Result.Error(1).fold(inc, dec); + // ==> inc(1) + + Result.Ok(1).fold(inc, dec); + // ==> dec(1) + + +@annotate-multi: [folktale.data.result.Error.prototype.merge, folktale.data.result.Ok.prototype.merge] +--- + +Returns the value inside of the Result structure, regardless of its state. + + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).merge(); + // ==> 1 + + Result.Error(1).merge(); + // ==> 1 + + +@annotate-multi: [folktale.data.result.Error.prototype.swap, folktale.data.result.Ok.prototype.swap] +--- + +Inverts the context of a Result value such that Errors are transformed into Oks, +and Oks are transformed into Errors. Does not touch the value inside of the +Result. + + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Ok(1).swap(); + // ==> Result.Error(1) + + Result.Error(1).swap(); + // ==> Result.Ok(1) + + +@annotate-multi: [folktale.data.result.Error.prototype.bimap, folktale.data.result.Ok.prototype.bimap] +--- + +Transforms each side of a Result with a function, without changing the context +of the computation. That is, Errors will still be Errors, Oks will still be +Oks. + + +## Example:: + + const Result = require('folktale/data/result'); + + const inc = (x) => x + 1; + const dec = (x) => x - 1; + + + Result.Ok(1).bimap(inc, dec); + // ==> Result.Ok(dec(1)) + + Result.Error(1).bimap(inc, dec); + // ==> Result.Error(inc(1)) + + +@annotate-multi: [folktale.data.result.Error.prototype.mapError, folktale.data.result.Ok.prototype.mapError] +--- + +Transforms the value inside an Error without changing the context of the +computation. +This is similar to `.map`, except it operates on Errors instead of Oks. + +## Example:: + + const Result = require('folktale/data/result'); + + Result.Error(1).mapError(x => x + 1); + // ==> Result.Error(2) + + Result.Ok(1).mapError(x => x + 1); + // ==> Result.Ok(1) + + +@annotate: folktale.data.result.of +--- + +Constructs a Result holding an Ok value. + + +## Example:: + + const Result = require('folktale/data/result'); + + Result.of(1); + // ==> Result.Ok(1) + + +@annotate: folktale.data.result.toValidation +--- + +Transforms a Result into a Validation. + +Result's `Ok`s are mapped into Validation's `Success`es, and Result's `Error`s +are mapped into Validation's `Failure`s. + + +## Example:: + + const Result = require('folktale/data/result'); + const Validation = require('folktale/data/validation'); + + Result.Ok(1).toValidation(); + // ==> Validation.Success(1) + + Result.Error(1).toValidation(); + // ==> Validation.Failure(1) + + +@annotate: folktale.data.result.toMaybe +--- + +Transforms a Result into a Maybe. Error values are lost in the process. + + +## Example:: + + const Result = require('folktale/data/result'); + const Maybe = require('folktale/data/maybe'); + + Result.Ok(1).toMaybe(); + // ==> Maybe.Just(1) + + Result.Error(1).toMaybe(); + // ==> Maybe.Nothing() + + diff --git a/src/data/result/result.js b/src/data/result/result.js index 4e03608..2a61f56 100644 --- a/src/data/result/result.js +++ b/src/data/result/result.js @@ -15,36 +15,27 @@ const adtMethods = require('folktale/helpers/define-adt-methods'); /*~ - * A data structure that models a disjunction, commonly used for modelling - * failures with an error object. - * --- - * category: Data structures - * stability: experimental - * authors: - * - "@boris-marinov" */ const Result = data('folktale:Data.Result', { /*~ - * Constructs an Result containing a Error value. * --- * category: Constructing * stability: experimental * type: | * forall a, b: (a) => Result a b */ - Error(value) { + Error(value) { return { value }; }, /*~ - * Constructs an Result containing a Ok value. * --- * category: Constructing * stability: experimental * type: | - * forall a, b: (b) => Result a b + * forall a, b: (b) => Result a b */ - Ok(value) { + Ok(value) { return { value }; } }).derive(setoid, show, serialize); @@ -60,13 +51,6 @@ const assertResult = assertType(Result); */ adtMethods(Result, { /*~ - * Transforms the `Ok` side of an `Result`. - * - * ## Example:: - * - * Error(1).map(x => x + 1); // ==> Result.Error(1) - * Ok(1).map(x => x + 1); // ==> Result.Ok(2) - * * --- * category: Transforming * stability: experimental @@ -89,17 +73,6 @@ adtMethods(Result, { /*~ - * Applies a function in an Result to the value of another. Both Results - * must be `Ok`. - * - * ## Example:: - * - * const inc = (x) => x + 1; - * - * Ok(inc).apply(Ok(1)); // ==> Result.Ok(2) - * Ok(inc).apply(Error(1)); // ==> Result.Error(1) - * Error(inc).apply(Error(1)); // ==> Result.Error(inc) - * * --- * category: Transforming * stability: experimental @@ -122,14 +95,6 @@ adtMethods(Result, { /*~ - * Transform the `Ok` side of Results into another Result. - * - * ## Example:: - * - * Error(1).chain(x => Ok(x + 1)); // ==> Result.Error(1) - * Ok(1).chain(x => Ok(x + 1)); // ==> Result.Ok(2) - * Ok(1).chain(x => Error(x + 1)); // ==> Result.Error(2) - * * --- * category: Transforming * signature: chain(transformation) @@ -150,37 +115,21 @@ adtMethods(Result, { } }, - - // NOTE: - // `get` is similar to Comonad's `extract`. The reason we don't implement - // Comonad here is that `get` is partial, and not defined for Error - // values. - /*~ - * Extracts the value of a `Ok` Result. - * - * Note that this method throws when called with a `Error`. In general - * it's recommended to use `.getOrElse` instead, where you can provide - * a failure value. - * - * ## Example:: - * - * Ok(1).get(); // ==> 1 - * * --- * category: Extracting values * stability: experimental - * signature: get() + * signature: unsafeGet() * type: | * forall a, b: (Result a b).() => b :: throws TypeError */ - get: { + unsafeGet: { Error() { - throw new TypeError(`Can't extract the value of a Error. + throw new TypeError(`Can't extract the value of an Error. Error does not contain a normal value - it contains an error. -You might consider switching from Result#get to Result#getOrElse, or some other method -that is not partial. +You might consider switching from Result#unsafeGet to Result#getOrElse, +or some other method that is not partial. `); }, @@ -191,14 +140,6 @@ that is not partial. /*~ - * Extracts the value of a `Ok` Result, or returns a fallback value - * if given a `Error`. - * - * ## Example:: - * - * Error(1).getOrElse(2); // ==> 2 - * Ok(1).getOrElse(2); // ==> 1 - * * --- * category: Extracting values * stability: experimental @@ -216,15 +157,8 @@ that is not partial. } }, - + /*~ - * Transforms a `Error` Result into a new Result. - * - * ## Example:: - * - * Error(1).orElse(x => Ok(2)); // ==> Result.Ok(2) - * Ok(1).orElse(x => Ok(2)); // ==> Result.Ok(1) - * * --- * category: Recovering * stability: experimental @@ -247,16 +181,6 @@ that is not partial. /*~ - * Applies a function to each side of an Result. - * - * ## Example:: - * - * const inc = (x) => x + 1; - * const dec = (x) => x - 1; - * - * Error(1).fold(inc, dec); // ==> inc(1) - * Ok(1).fold(inc, dec); // ==> dec(1) - * * --- * category: Pattern matching * stability: experimental @@ -281,13 +205,6 @@ that is not partial. /*~ - * Returns the value of an Result regardless of its tag. - * - * ## Example:: - * - * Error(1).merge(); // ==> 1 - * Ok(2).merge(); // ==> 2 - * * --- * category: Extracting values * stability: experimental @@ -307,13 +224,6 @@ that is not partial. /*~ - * Transform Errors into Oks, and vice-versa. - * - * ## Example:: - * - * Error(1).swap(); // ==> Result.Ok(1) - * Ok(1).swap(); // ==> Result.Error(1) - * * --- * category: Transforming * stability: experimental @@ -322,8 +232,8 @@ that is not partial. * forall a, b: (Result a b).() => Result b a */ swap: { - Error() { - return Ok(this.value); + Error() { + return Ok(this.value); }, Ok() { @@ -333,16 +243,6 @@ that is not partial. /*~ - * Transforms each side of an Result with a function. - * - * ## Example:: - * - * const inc = (x) => x + 1; - * const dec = (x) => x - 1; - * - * Error(1).bimap(inc, dec); // ==> Result.Error(inc(1)) - * Ok(1).bimap(inc, dec); // ==> Result.Ok(dec(1)) - * * --- * category: Transforming * stability: experimental @@ -366,29 +266,22 @@ that is not partial. /*~ - * Transforms the value of Error Results. - * - * ## Example:: - * - * Error(1).errorMap(x => x + 1); // ==> Result.Error(2) - * Ok(1).errorMap(x => x + 1); // ==> Result.Ok(1) - * * --- * category: Transforming * stability: experimental - * signature: errorMap(transformation) + * signature: mapError(transformation) * type: | * forall a, b, c: * (Result a b).((a) => c) => Result c b */ - errorMap: { + mapError: { Error(f) { - assertFunction('Result.Error#errorMap', f); + assertFunction('Result.Error#mapError', f); return Error(f(this.value)); }, Ok(f) { - assertFunction('Result.Ok#errorMap', f); + assertFunction('Result.Ok#mapError', f); return this; } } @@ -397,12 +290,6 @@ that is not partial. Object.assign(Result, { /*~ - * Constructs an Result holding a Ok value. - * - * ## Example:: - * - * Result.of(1); // ==> Result.Ok(1) - * * --- * category: Constructing * stability: experimental @@ -413,17 +300,19 @@ Object.assign(Result, { return Ok(value); }, + /*~ + * --- + * category: Extracting values + * stability: deprecated + * type: | + * forall a, b: (Result a b).() => b :: (throws TypeError) + */ + 'get'() { + warnDeprecation('`.get()` is deprecated, and has been renamed to `.unsafeGet()`.'); + return this.unsafeGet(); + }, /*~ - * Transforms an Result into a Validation. - * - * ## Example:: - * - * const { Failure, Success } = require('folktale/data/validation'); - * - * Result.Error(1).toValidation(); // ==> Failure(1) - * Result.Ok(1).toValidation(); // ==> Success(1) - * * --- * category: Converting * stability: experimental @@ -436,15 +325,6 @@ Object.assign(Result, { /*~ - * Transforms an Result to a Maybe. Error values are lost in the process. - * - * ## Example:: - * - * const { Nothing, Just } = require('folktale/data/maybe'); - * - * Result.Error(1).toMaybe(); // ==> Nothing() - * Result.Ok(1).toMaybe(); // ==> Just(1) - * * --- * category: Converting * stability: experimental diff --git a/test/specs-src/data.result.js b/test/specs-src/data.result.js index 703ab27..c10b9bf 100644 --- a/test/specs-src/data.result.js +++ b/test/specs-src/data.result.js @@ -99,12 +99,12 @@ describe('Data.Result', function() { return _.Ok(a).bimap(id, f).equals(_.Ok(f(a))) }); - property('Error#errorMap', 'json', 'json -> json', (a, f) => { - return _.Error(f(a)).equals(_.Error(a).errorMap(f)) + property('Error#mapError', 'json', 'json -> json', (a, f) => { + return _.Error(f(a)).equals(_.Error(a).mapError(f)) }); - property('Ok#errorMap', 'json', 'json -> json', (a, f) => { - return _.Ok(a).errorMap(f).equals(_.Ok(a)) + property('Ok#mapError', 'json', 'json -> json', (a, f) => { + return _.Ok(a).mapError(f).equals(_.Ok(a)) }); }); describe('Conversions', () => {