From 9ddda03bd6e863132dcaababa56230997c842da3 Mon Sep 17 00:00:00 2001 From: Eric Rumsey Date: Fri, 23 May 2025 17:36:31 -0500 Subject: [PATCH] Functional programming module --- src/lib/util/basic/either.ts | 28 +++++++++++++++++++++++++++ src/lib/util/basic/list.ts | 36 +++++++++++++++++++++++++++++++++++ src/lib/util/basic/option.ts | 22 +++++++++++++++++++++ src/lib/util/basic/utility.ts | 22 +++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/lib/util/basic/either.ts create mode 100644 src/lib/util/basic/list.ts create mode 100644 src/lib/util/basic/option.ts create mode 100644 src/lib/util/basic/utility.ts diff --git a/src/lib/util/basic/either.ts b/src/lib/util/basic/either.ts new file mode 100644 index 0000000..e7e460f --- /dev/null +++ b/src/lib/util/basic/either.ts @@ -0,0 +1,28 @@ +export type Left = {readonly _tag: 'Left', readonly left: E} +export type Right = {readonly _tag: 'Right', readonly right: A} +export type Either = Left | Right + +// Constructors +export const left = (e: E): Either => ({ + _tag: 'Left', + left: e, +}) +export const right = (a: A): Either => ({ + _tag: 'Right', + right: a, +}) + +// Operations +export const eitherMap = + (f: (a: A) => B) => (fa: Either): Either => + fa._tag === 'Right' ? right(f(fa.right)) : fa + +export const eitherChain = + (f: (a: A) => Either) => + (fa: Either): Either => + fa._tag === 'Right' ? f(fa.right) : fa + +export const fold = + (onLeft: (e: E) => B, onRight: (a: A) => B) => + (fa: Either): B => + fa._tag === 'Left' ? onLeft(fa.left) : onRight(fa.right) diff --git a/src/lib/util/basic/list.ts b/src/lib/util/basic/list.ts new file mode 100644 index 0000000..ffc672f --- /dev/null +++ b/src/lib/util/basic/list.ts @@ -0,0 +1,36 @@ +export type Nil = { readonly _tag: 'Nil' }; +export type Cons = { + readonly _tag: 'Cons'; + readonly head: A; + readonly tail: List; +}; +export type List = Nil | Cons; + +// Constructors +export const nil: Nil = { _tag: 'Nil' }; +export const cons = (head: A, tail: List): List => ({ + _tag: 'Cons', + head, + tail, +}); + +// Operations +type ListMap = (f: (a: A) => B) => (fa: List) => List; +export const listMap: ListMap = (f) => (fa) => + fa._tag === 'Cons' ? cons(f(fa.head), listMap(f)(fa.tail)) : nil; + +type ListReduce = ( + f: (b: B, a: A) => B, + initial: B +) => (fa: List) => B; +export const listReduce: ListReduce = (f, initial) => (fa) => + fa._tag === 'Cons' ? listReduce(f, f(initial, fa.head))(fa.tail) : initial; + +// Helpers +type FromArray = (arr: Array) => List; +export const fromArray: FromArray = (arr: Array) => + arr.reduceRight((acc: List, val: A) => cons(val, acc), nil as List); + +type ToArray = (fa: List) => Array; +export const toArray: ToArray = (fa: List) => + listReduce>((acc, val) => [...acc, val], [] as Array)(fa); diff --git a/src/lib/util/basic/option.ts b/src/lib/util/basic/option.ts new file mode 100644 index 0000000..165c0c1 --- /dev/null +++ b/src/lib/util/basic/option.ts @@ -0,0 +1,22 @@ +export type None = {readonly _tag: 'None'} +export type Some = {readonly _tag: 'Some', readonly value: A} +export type Option = Some | None + +// Constructors +export const none: Option = {_tag: 'None'} +export const some = (value: A): Option => ({ + _tag: 'Some', + value, +}) + +// Operations +export const map = + (f: (a: A) => B) => (fa: Option): Option => + fa._tag === 'Some' ? some(f(fa.value)) : none + +export const chain = + (f: (a: A) => Option) => (fa: Option): Option => + fa._tag === 'Some' ? f(fa.value) : none + +export const getOrElse = (defaultValue: A) => (fa: Option): A => + fa._tag === 'Some' ? fa.value : defaultValue diff --git a/src/lib/util/basic/utility.ts b/src/lib/util/basic/utility.ts new file mode 100644 index 0000000..b7dd0b5 --- /dev/null +++ b/src/lib/util/basic/utility.ts @@ -0,0 +1,22 @@ +import { type Either, left, right } from './either' +import { none, type Option, some } from './option' + +// Pipe function for composition +export const pipe = ( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, +): C => bc(ab(a)) + +// TryCatch helper using Either +export const tryCatch = (f: () => A): Either => { + try { + return right(f()) + } catch (e) { + return left(e instanceof Error ? e : new Error(String(e))) + } +} + +// Option from nullable +export const fromNullable = (a: A | null | undefined): Option => + a == null ? none : some(a)