The Result type
Premise
First if you are unfamiliar with the concept I highly recommend that you read Railway Programming.
This page will go through some explanation of this implementation of the Result type. The API design was heavily influenced by the Elm Result. One thing that was nice is that Typescript supports variadic function inference. So instead of having map,map2,map3,map4,map5 we simply have map that takes any number of arguments.
Why
tbd
Example
You want to generate a driving license only for canadians 16 years old and older.
import { success, failure, Result } from "typegate";
class NotCanadian extends Error { readonly tag = 'NotCanadian' };
class YoungerThan16 extends Error { readonly tag = 'YoungerThan16'};
type PossibleError = NotCanadian | YoungerThan16;
function createCanadianDrivingLicense(person: Person): Result<PossibleError, DrivingLicense> {
if (person.country !== "Canada"){
return failure(new NotCanadian());
}
if (person.age < 16) {
return failure(new YoungerThan16());
}
return success(new DrivingLicense());
}
success
import { success } from "typegate";
const result: Result<string, number> = success(1337);
failure
import { failure } from "typegate";
const result: Result<string, number> = failure("Something went wrong!");
map
That function is available in the Result module. It is not a method available on a Result instance like Result.map. It allows you to call an n-arity function that does not care about Result. That means you can call a bunch of functions that could fail and if they all success you send the values to the target function. If there's one failure, you return the first one.
import { map, Result } from "typegate";
function buildGroup(): Result<BuildGroupError, Person[]>;
function loadFile(path: string): Result<LoadFileError, File>;
function generateReport(employees: Person[], file: File, kind: ReportType): Report;
const result: Result<BuildGroupError | LoadFileError, Report> =
map(generateReport)(
buildGroup(),
loadFile('./theFile'),
success(ReportKind.quarter)
);
And you have auto-complete on all the arguments.
TODO: Make the map
function stop when it encounters a failure. Right now we are evaluating all the other branches, so that could be expensive for nothing depending on the usecase.
Result.map
A function to describe another transformation to apply when the Result is a success. It won't run if the Result is an error.
import { success } from "typegate";
const result: Result<unknown, number> = success(4);
const result.map(e => 2 * e);
if (result.success) {
console.log(result.value); // outputs: 8
}
Result.mapError
A function to describe another transformation to apply when the Result is failure. It won't run if the Result is a success.
import { failure } from "typegate";
const result: Result<string, unknown> = failure("Something is wrong);
const result.mapError(e => e + "!");
if (!result.success) {
console.log(result.error); // outputs: "Something is wrong!"
}
Result.withDefault
That function gets the value within the success. If the result is a failure, it returns the fallback value that you've passed to it instead.
import { failure } from "typegate";
const valueA = failure("Something is wrong").withDefault(14);
console.log(valueA); // outputs: "14"
const valueB = success(32).withDefault(14);
console.log(valueB); // outputs: "32"
Result.andThen
TODO
Result.orElse
TODO
Result.unsafe
Since there are no macros in typescript, if you know ahead of time that something is going to work and that you are fine with it blowing up with an exception if that's not the case, then you can use unsafe to unwrap the value within the result.