Skip to content

Instantly share code, notes, and snippets.

@tomiolah
Last active January 19, 2024 17:00
Show Gist options
  • Save tomiolah/d5e4cc37db7221a8be464a39d447f80e to your computer and use it in GitHub Desktop.
Save tomiolah/d5e4cc37db7221a8be464a39d447f80e to your computer and use it in GitHub Desktop.
TS Utilities
/** Represents a value that may or may not be present. */
export type Maybe<T> =
| { verdict: "ok"; value: T }
| { verdict: "err"; error: string };
/** Wraps a maybe value in a MaybeContainer.
*
* Why would you want to do this? Because it allows you to chain functions together using the pipe method.
*
* @param maybeValue - The maybe value to wrap.
* @returns A MaybeContainer containing the maybe value.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = wrap(maybeValue);
* const result = maybeContainer.handle((value) => value + 1);
* console.log(result); // 43
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = wrap(maybeValue).pipe((value) => value + 1);
* console.log(result); // 43
*/
export class MaybeContainer<T> {
value: Maybe<T>;
/** Creates a new MaybeContainer instance by wrapping a maybe value.
*
* @param value - The maybe value to wrap.
*/
constructor(value: Maybe<T>) {
this.value = value;
}
/** Handles the maybe value.
*
* @param onOk - The function to call if the maybe value is ok.
* @param onErr - The function to call if the maybe value is an error.
* @returns The result of the function that was called.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = new MaybeContainer(maybeValue);
* const result = maybeContainer.handle((value) => value + 1);
* console.log(result); // 43
* @example
* const maybeValue: Maybe<number> = { verdict: "err", error: "Something went wrong" };
* const maybeContainer = new MaybeContainer(maybeValue);
* const result = maybeContainer.handle((value) => value + 1, (err) => err);
* console.log(result); // "Something went wrong"
*/
handle<RetVal = T>(
onOk: (value: T) => Maybe<RetVal>,
onErr: (err: string) => Maybe<RetVal> = (err) => {
console.error(err);
return { verdict: "err", error: err };
}
): Maybe<RetVal> {
if (this.value.verdict === "ok") {
return onOk(this.value.value);
}
return onErr(this.value.error);
}
/** Unpacks the maybe value or panics (throws an error) if it is an error.
*
* @returns The value of the maybe value. If the maybe value is an error, this function will throw an error.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = new MaybeContainer(maybeValue);
* const result = maybeContainer.unpackOrPanic();
* console.log(result); // 42
* @example
* const maybeValue: Maybe<number> = { verdict: "err", error: "Something went wrong" };
* const maybeContainer = new MaybeContainer(maybeValue);
* const result = maybeContainer.unpackOrPanic(); // Throws an error
*/
unpackOrPanic(): T | undefined {
if (this.value.verdict === "ok") {
return this.value.value;
}
console.error(this.value.error);
throw new Error(this.value.error);
}
/** Wraps a maybe value in a MaybeContainer (same as the constructor).
*
* @param value - The maybe value to wrap.
* @returns A MaybeContainer containing the maybe value.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = MaybeContainer.wrap(maybeValue);
*/
static wrap<X>(value: Maybe<X>): MaybeContainer<X> {
return new MaybeContainer(value);
}
/** Executes a function on the maybe value and returns the result in a new MaybeContainer.
* This allows you to chain functions together.
*
* @param fn - The function to execute on the maybe value.
* @returns A new MaybeContainer containing the result of the function.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = new MaybeContainer(maybeValue);
* const result = maybeContainer.pipe((value) => value + 1).pipe((value) => value + 1);
* console.log(result); // 44
*/
pipe<RetVal>(fn: (value: T) => Maybe<RetVal>): MaybeContainer<RetVal> {
return new MaybeContainer(this.handle(fn));
}
}
export const M = {
/** Wraps a maybe value in a MaybeContainer.
* Same as MaybeContainer.wrap() or new MaybeContainer().
*
* @param maybeValue - The maybe value to wrap.
* @returns A MaybeContainer containing the maybe value.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const maybeContainer = wrap(maybeValue);
* const result = maybeContainer.handle((value) => value + 1);
* console.log(result); // 43
*/
wrap: <T>(maybeValue: Maybe<T>): MaybeContainer<T> =>
MaybeContainer.wrap(maybeValue),
/** Creates a maybe value that is ok.
*
* @param value - The value to wrap in a maybe value.
* @returns A maybe value that is ok.
* @example
* const maybeValue = Ok(42);
* console.log(maybeValue); // { verdict: "ok", value: 42 }
*/
Ok: <T>(value: T): Maybe<T> => ({ verdict: "ok", value }),
/** Creates a maybe value that is an error.
*
* @param error - The error message to wrap in a maybe value.
* @returns A maybe value that is an error.
* @example
* const maybeValue = Err("Something went wrong");
* console.log(maybeValue); // { verdict: "err", error: "Something went wrong" }
*/
Err: <T>(error: string): Maybe<T> => ({ verdict: "err", error }),
/** Unpacks a maybe value or panics (throws an error) if it is an error.
*
* @param maybeValue - The maybe value to unpack.
* @returns The value of the maybe value. If the maybe value is an error, this function will throw an error.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const result = unpackOrPanic(maybeValue);
* console.log(result); // 42
* @example
* const maybeValue: Maybe<number> = { verdict: "err", error: "Something went wrong" };
* const result = unpackOrPanic(maybeValue); // Throws an error
*/
unpackOrPanic: <T>(maybeValue: Maybe<T>): T | undefined => {
if (maybeValue.verdict === "ok") {
return maybeValue.value;
}
console.error(maybeValue.error);
throw new Error(maybeValue.error);
},
/**
* Handles a maybe value.
*
* @param maybeValue - The maybe value to handle.
* @param onOk - The function to call if the maybe value is ok.
* @param onErr - The function to call if the maybe value is an error.
* @returns The result of the function that was called.
* @example
* const maybeValue: Maybe<number> = { verdict: "ok", value: 42 };
* const result = handleMaybe(maybeValue, (value) => value + 1);
* console.log(result); // 43
* @example
* const maybeValue: Maybe<number> = { verdict: "err", error: "Something went wrong" };
* const result = handleMaybe(maybeValue, (value) => value + 1, (err) => err);
* console.log(result); // "Something went wrong"
*/
handleMaybe: <T, RetVal = T>(
maybeValue: Maybe<T>,
onOk: (value: T) => Maybe<RetVal>,
onErr: (err: string) => Maybe<RetVal> = (err) => {
console.error(err);
return { verdict: "err", error: err };
}
): Maybe<RetVal> => {
if (maybeValue.verdict === "ok") {
return onOk(maybeValue.value);
}
return onErr(maybeValue.error);
},
};
/**
* Creates a sorting function based on the provided compare function.
* @param compareFn - The compare function used to determine the order of elements.
* @returns A sorting function that can be used with Array.sort().
*/
export function sortFn<T>(compareFn: (a: T, b: T) => "a>b" | "a<b" | "a=b") {
return (a: T, b: T) => {
const result = compareFn(a, b);
if (result === "a>b") return 1;
if (result === "a<b") return -1;
return 0;
};
}
/** Compare function helper
*
* Using the `Array.sort` method is usually rather confusing, so this helper
* function makes it easier to understand what's going on.
* It translates easy-to-understand comparison results like "a<b", "a>b" or "a=b"
* to the -1, 1 or 0 values that the `Array.sort` method expects.
*
* @param cmp - The comparison function that returns "a<b", "a>b" or "a=b" depending on the comparison result.
* This result is then converted to -1, 1 or 0, respectively, to be used by the Array.sort method
* @returns a function that can be used as a parameter for the Array.sort method to compare two values
*/
export const compareFn =
<T>(cmp: (a: T, b: T) => "a<b" | "a>b" | "a=b") =>
(a: T, b: T) => {
const result = cmp(a, b);
if (result === "a<b") {
return -1;
}
if (result === "a>b") {
return 1;
}
return 0;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment