Skip to content

Instantly share code, notes, and snippets.

@robinpokorny
Created November 3, 2022 08:47
Show Gist options
  • Save robinpokorny/7bcde83da63463979e4916aa992192e4 to your computer and use it in GitHub Desktop.
Save robinpokorny/7bcde83da63463979e4916aa992192e4 to your computer and use it in GitHub Desktop.
Heapcon 2022: Railway Oriented Typescript
/*
=============================
Railway Oriented Typescript
=============================
by @robinpokorny
*/
/* === 1. Union basics === */
const a: string | number = 42;
const b: "hey" | "ahoy" = "hey";
const c: "hey" | "ahoy" = "heyyyy"; // Error
const code: 200 | 400 | 500 = 200;
const f = (a: { name: string } | { id: number }) => {
a.name; // Property 'name' does not exist on type '{ name: string; } | { age: number; }'.
if ("name" in a) {
a.name;
}
};
type Person = { tag: "person"; name: string };
type Robot = { tag: "robot"; id: number };
const g = (a: Person | Robot) => {
if (a.tag === "person") {
a.name;
a.id; // Error
}
};
/* === 2. Success, Failure, Result === */
type Success<T> = {
tag: "success";
value: T;
};
type Failure<E> = {
tag: "failure";
value: E;
};
type Result<T, E> = Success<T> | Failure<E>;
/* === 3. Use Result === */
const validate = (input: unknown): Result<number, "NOT_AN_INTEGER"> => {
if (Number.isInteger(input)) {
return {
tag: "success",
value: input as number,
};
} else {
return {
tag: "failure",
value: "NOT_AN_INTEGER",
};
}
};
const updateDB = (input: number): Result<Date, "DB_UNAVAILABLE"> => {
DB.write(input);
if (DB.success) {
return {
tag: "success",
value: new Date(),
};
} else {
return {
tag: "failure",
value: "DB_UNAVAILABLE",
};
}
};
const log = (value: unknown) => console.log("[RP]:", value);
/* === 4. Chain and Tee === */
const chain =
<T, S, E>(f: (a: T) => Result<S, E>) =>
<F>(result: Result<T, F>): Result<S, E | F> => {
if (result.tag === "failure") {
return result;
}
return f(result.value);
};
const chainedUpdateDB = chain(updateDB);
const tee =
<T, E>(f: (a: unknown) => void) =>
(result: Result<T, E>): Result<T, E> => {
if (result.tag === "success") {
f(result.value);
}
return result;
};
// (result: Result<unknown, unknown>) => Result<unknown, unknown>
const teedLog = tee(log);
const action = (input: unknown) => {
// pipe(a, b, c) == c(b(a))
// Result<Date, "DB_UNAVAILABLE" | "NOT_AN_INTEGER">
const response = pipe(input, validate, chain(updateDB), tee(log));
if (response.tag === "failure") {
switch (response.value) {
case "DB_UNAVAILABLE":
return "…";
case "NOT_AN_INTEGER":
return "…";
case "NOT_AN_ARRAY":
return "…";
}
if (response) {
// Unreachable code detected
}
}
return response.value;
};
// A: `string`-`number` mismatch
// B: Forgot `chain`
// UTILS
const DB = {
success: false,
write: (x: unknown) => {},
};
import { pipe } from "fp-ts/function";
export const input = 3;
[a, b, c, code, action, f, g, chainedUpdateDB, teedLog];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment