Skip to content
This repository has been archived by the owner on Apr 2, 2023. It is now read-only.

Commit

Permalink
feat: add super basic immutable cons list
Browse files Browse the repository at this point in the history
  • Loading branch information
tdreyno committed May 3, 2020
1 parent c8e50ca commit 4a69b83
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
export const isUndefined = (data: unknown): data is undefined =>
data === undefined

export const isNull = (data: unknown): data is null => data === null

export const isFunction = (data: unknown): data is Function =>
typeof data === "function"

Expand Down Expand Up @@ -176,7 +178,7 @@ export function isPlainObject(obj: unknown) {

export const let_ = <Args extends any[], R>(
expression: (...vars: Args) => R,
) => (vars: Args): R => expression(...vars)
) => (...vars: Args): R => expression(...vars)

export const withDefault = <T>(fallback: () => T) => (
value: T | undefined,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./core/index"
export * from "./memo/index"
export * from "./monads/index"
export * from "./list/index"

// interface Setoid {
// equals(other: Setoid): boolean
Expand Down
72 changes: 72 additions & 0 deletions src/list/List.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { let_, pipe } from "../core/index"

export type Nil = undefined
export type Cons<T> = [T, Cons<T> | Nil]
export type List<T> = Nil | Cons<T>
export type NonEmptyList<T> = Cons<T>

export const Nil = <T = unknown>(): List<T> => undefined
export const cons = <T>(head: T) => (list: List<T>): List<T> => [head, list]
export const head = <T>(list: List<T>) =>
list !== undefined ? list[0] : undefined
export const tail = <T>(list: List<T>) =>
list !== undefined ? list[1] : undefined

export const fromArray = <T>(items: T[]) =>
items.reverse().reduce((sum, item) => cons(item)(sum), Nil<T>())

export const of = <T>(...args: T[]) => fromArray(args)
export const empty = Nil

export const map = <A, B>(fn: (a: A) => B) => (list: List<A>): List<B> =>
let_<[A | undefined, List<A>], List<B>>((h, t) =>
h !== undefined ? [fn(h), map(fn)(t)] : Nil<B>(),
)(head(list), tail(list))

export const filter = <A>(fn: (a: A) => unknown) => (list: List<A>): List<A> =>
let_<[A | undefined, List<A>], List<A>>((h, t) =>
h !== undefined ? (fn(h) ? [h, filter(fn)(t)] : filter(fn)(t)) : Nil<A>(),
)(head(list), tail(list))

export const some = <A>(fn: (a: A) => unknown) => (list: List<A>): boolean =>
let_<[A | undefined, List<A>], boolean>((h, t) =>
h !== undefined ? (fn(h) ? true : some(fn)(t)) : false,
)(head(list), tail(list))

export const every = <A>(fn: (a: A) => unknown) => (list: List<A>): boolean =>
let_<[A | undefined, List<A>], boolean>((h, t) =>
h !== undefined ? (!fn(h) ? false : every(fn)(t)) : true,
)(head(list), tail(list))

export const reduce = <T, A>(fn: (acc: T, a: A) => T) => (acc: T) => (
list: List<A>,
): T =>
let_<[A | undefined, List<A>], T>((h, t) =>
h !== undefined ? reduce(fn)(fn(acc, h))(t) : acc,
)(head(list), tail(list))

export const toArray = <T>(list: List<T>): T[] =>
reduce<T[], T>((acc, item) => {
acc.push(item)
console.log("hey", item, acc)

return acc
})([])(list)

export const size = reduce((acc: number) => acc + 1)(0)

export const conj = <A>(item: A) => (list: List<A>): List<A> =>
let_<[A | undefined, List<A>], List<A>>((h, t) =>
h !== undefined ? cons(h)(conj(item)(t)) : of(item),
)(head(list), tail(list))

export const last = <A>(list: List<A>): A | undefined =>
let_<[A | undefined, List<A>], A | undefined>((h, t) =>
h !== undefined ? (t !== undefined ? last(t) : h) : undefined,
)(head(list), tail(list))

export const reverse = <A>(list: List<A>) =>
reduce<List<A>, A>((acc, item) => cons(item)(acc))(Nil<A>())(list)

export const removeLast = <A>(list: List<A>): List<A> =>
pipe(reverse, tail, reverse)(list)
105 changes: 105 additions & 0 deletions src/list/__tests__/List.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
head,
tail,
empty,
of,
fromArray,
toArray,
map,
filter,
some,
every,
size,
reverse,
cons,
conj,
reduce,
last,
removeLast,
} from "../List"

describe("List", () => {
test("empty", () => {
expect(head(empty())).toBeUndefined()
expect(tail(empty())).toBeUndefined()
})

test("of", () => {
const hasFive = of(5)
expect(head(hasFive)).toBe(5)
expect(tail(hasFive)).toBeUndefined()
})

test("fromArray", () => {
const list = fromArray([1, 2, 3])
expect(toArray(list)).toEqual([1, 2, 3])
})

test("map", () => {
const list = fromArray([1, 2, 3])
const mappedList = map((a: number) => a + 1)(list)
expect(toArray(mappedList)).toEqual([2, 3, 4])
})

test("filter", () => {
const list = fromArray([1, 2, 3, 4])
const mappedList = filter((a: number) => a % 2 === 0)(list)
expect(toArray(mappedList)).toEqual([2, 4])
})

test("some", () => {
const list = fromArray([1, 2, 3, 4])

const someListA = some((a: number) => a > 3)(list)
expect(someListA).toBe(true)

const someListB = some((a: number) => a < 0)(list)
expect(someListB).toBe(false)
})

test("every", () => {
const list = fromArray([1, 2, 3, 4])

const someListA = every((a: number) => a > 0)(list)
expect(someListA).toBe(true)

const someListB = every((a: number) => a > 3)(list)
expect(someListB).toBe(false)
})

test("size", () => {
const list = fromArray([1, 2, 3, 4])
expect(size(list)).toBe(4)
})

test("reduce", () => {
const list = fromArray([1, 2, 3])
const sum = reduce((acc: number, a: number) => acc + a)(0)(list)
expect(sum).toBe(6)
})

test("cons", () => {
const list = fromArray([1, 2])
expect(toArray(cons(0)(list))).toEqual([0, 1, 2])
})

test("conj", () => {
const list = fromArray([1, 2])
expect(toArray(conj(3)(list))).toEqual([1, 2, 3])
})

test("last", () => {
const list = fromArray([1, 2, 3])
expect(last(list)).toBe(3)
})

test("removeLast", () => {
const list = fromArray([1, 2, 3])
expect(toArray(removeLast(list))).toEqual([1, 2])
})

test("reverse", () => {
const list = fromArray([1, 2, 3, 4])
expect(toArray(reverse(list))).toEqual([4, 3, 2, 1])
})
})
1 change: 1 addition & 0 deletions src/list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as List from "./List"

0 comments on commit 4a69b83

Please sign in to comment.