Skip to content

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.