Skip to content

Instantly share code, notes, and snippets.

@robinpokorny
Last active June 13, 2022 08:51
Show Gist options
  • Save robinpokorny/fbbff6908dd54072831694ebea3de3c1 to your computer and use it in GitHub Desktop.
Save robinpokorny/fbbff6908dd54072831694ebea3de3c1 to your computer and use it in GitHub Desktop.
WebExpo 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 } | { age: 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 Pet = { tag: "pet"; age: number };
const g = (a: Person | Pet) => {
if (a.tag === "person") {
a.name;
a.age; // 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" as const,
};
}
};
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);
};
// <F>(result: Result<number, F>) => Result<Date, "DB_UNAVAILABLE" | F>
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
}
}
};
// A: `string`-`number` mismatch
// B: Forgot `chain`
// UTILS
const DB = {
success: false,
write: (x: unknown) => {},
};
export const input = 3;
import { pipe } from "fp-ts/function";
[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