Created
November 15, 2018 21:51
-
-
Save rjdestigter/1447d55e25af6728732140ba9b10ae7f to your computer and use it in GitHub Desktop.
Lazy list experiment in JavScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Type describing a "lazy" value, a.k.a a function returnting the value | |
*/ | |
type Lazy<A> = () => A | |
/** | |
* Continued | |
*/ | |
class Cons<A> { | |
constructor(readonly head: Lazy<A>, readonly tail: Lazy<List<A>>) {} | |
} | |
const a: any = null | |
/** | |
* Empty list | |
*/ | |
interface Nil<A> { | |
type: 'NIL' | |
value: A | |
} | |
/** | |
* Singleton describing an empty list | |
*/ | |
const NIL: List<any> = { | |
type: 'NIL', | |
value: a, | |
} | |
/** | |
* Helper function to assign an element type to an empty list | |
*/ | |
const nil = <A>(): List<A> => NIL | |
/** | |
* Linked list | |
*/ | |
type List<A> = Cons<A> | Nil<A> | |
/** | |
* Creates a linked list with a given head and tail | |
*/ | |
const cons = <A>(head: Lazy<A>, tail: Lazy<List<A>>): List<A> => new Cons(head, tail) | |
/** | |
* Determines if a given list is an empty list | |
*/ | |
const isNil = <T>(list: List<T>): list is Nil<T> => NIL === list | |
/** | |
* foldRight - Right-associative fold of a structure. | |
* @param f Reducer, given A, returns a lazy B. | |
* @param b Lazy initial value | |
* @param list The list to be folded | |
* @return The result of the final fold | |
* @example Summing a list using foldRight: | |
* `foldRight((x, y) => x + y)(0)(list)` // where "list" is of type `List<number>` | |
*/ | |
const foldRight = <A, B>(f: (a: A, b: Lazy<B>) => B) => (b: Lazy<B>) => (list: List<A>): B => { | |
if (isNil(list)) { | |
return b() | |
} | |
const h = list.head() | |
const t = list.tail() | |
console.warn(`${h} [H] evaluated in foldRight`) | |
return f(h, () => foldRight(f)(b)(t)) | |
} | |
/** | |
* append - Append list B to the tail of list A | |
* @param lista The list to append to | |
* @param listb The list to append to list Append | |
* @returns The two lists appended into one list | |
*/ | |
const append = <A>(lista: List<A>) => (listb: List<A>) => | |
/** | |
* We can express "append" in terms of "foldRight" | |
*/ | |
foldRight( | |
/** | |
* On the first run the reducer takes the head of List A and the initial value, List B | |
* and returns a new list. | |
*/ | |
(h: A, t: Lazy<List<A>>) => cons(() => h, t) | |
)( | |
// The initial value is "List B" | |
() => listb | |
)( | |
// The list being "folded" is "List A" | |
lista | |
) | |
/** | |
* Map a list form a - b | |
* @param f | |
*/ | |
const map = <A, B>(f: (a: A) => B) => (list: List<A>): List<B> => | |
foldRight((h: A, t: Lazy<List<B>>) => cons(() => f(h), t))(() => nil<B>())(list) | |
/** | |
* infinite - Create an infinite linked list of numbers starting at the given number. | |
* @param n The starting number | |
* @returns An infinite linked list of numbers | |
*/ | |
const infinite = (n: number): List<number> => cons(() => n, () => infinite(n + 1)) | |
/** | |
* take - Take n elements from the list | |
* @param n The number of elements to take | |
*/ | |
const take = (n: number) => <A>(list: List<A>): List<A> => { | |
if (isNil(list)) { | |
return list | |
} | |
return cons(list.head, () => { | |
if (n - 1 >= 1) { | |
return take(n - 1)(list.tail()) | |
} | |
return nil<A>() | |
}) | |
} | |
/** | |
* filter - Filter a linked list with a given predicate function | |
* @param f - Filter function that given an element in the list returns `true` or `false` | |
*/ | |
const filter = <A>(f: (a: A) => boolean) => (list: List<A>): List<A> => { | |
const empty: Lazy<List<A>> = () => nil<A>() | |
const filterP = (h: A, t: Lazy<List<A>>) => { | |
if (f(h)) { | |
return cons(() => h, t) | |
} | |
return t() | |
} | |
return foldRight(filterP)(empty)(list) | |
} | |
/** | |
* toArray - Convert a linked list to an array | |
* @param list The linked list to be converted | |
* @returns A an array of all elements in the linked list | |
*/ | |
const toArray = <A>(list: List<A>): A[] => foldRight((h: A, arr: () => A[]) => [h].concat(arr()))(() => [])(list) | |
// Infinite list of number starting at 1 | |
const listOfInfiniteNumbers = infinite(1) | |
const filterEvens = filter((n: number) => n % 2 === 0) | |
const mapToDoubles = map((n: number) => n * 2) | |
const infiniteDoubleEvens = filterEvens(mapToDoubles(listOfInfiniteNumbers)) | |
console.log(toArray(take(10)(infiniteDoubleEvens))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment