import {and, False, If, not, True} from './booleans'
import {first, pair, second} from './pairs'
import {lt} from './predicates'
import {add, pred, sub, succ, zero} from './numerals'
import {and, False, If, not, True} from './booleans'
import {first, pair, second} from './pairs'
import {lt} from './predicates'
import {add, pred, sub, succ, zero} from './numerals'
Now lists are really cool. There are a few ways to implement them, this is how I’ve done it
nil
represents the end of a list.
It’s necessary to define this so iterating functions know when to complete.
All other nodes will have False
as the first value in their pair
export const nil = pair(True)(True)
isNil
returns True
if applied to nil
and False
otherwise
isNil(nil) // => True
isNil(pair(False)(someValue)) // => False
export const isNil = first
cons
takes two values and returns a new node of those values
const list123 = cons(one)(cons(two)(cons(three)(nil)))
// => This is our basic list of [one two three]
export const cons = a => b => pair(False)(pair(a)(b))
export const head = a => first(second(a))
tail
takes a list and returns a list of all values except the first
tail(list123) // => list of [two three]
export const tail = a => second(second(a))
So now we have these simple functions defined we can build all our favourite list functions!
range
takes two numerals and returns a new list of numbers from the first argument up to and including the second
range(one)(three) // => list of [one two three]
range(three)(four) // => list of [three four]
export const range = a => b => sub(succ(b))(a)(c => cons(sub(b)(length(c)))(c))(nil)
repeat
takes a value and a numeral and returns a new list of length specified by second arguments filled with the provided value
repeat(one)(three) // => list of [one one one]
repeat(True)(four) // => list of [True True True True]
export const repeat = a => b => b(c => cons(a)(c))(nil)
When you can fold you can derive all sorts of useful functions as we shall see shortly
This is the Y combinator which we use to recursively call lambda exressions in our definitions below
const Y = a => (b => b(b))(b => a(c => b(b)(c)))
foldr
takes a reducing function (this takes two argumens, the list element then the accumulator), an initial value and a list. It then iterates over the list from right to left applying the reducing funcion to the initial value / result of previous reducing function and the current value in the list.
foldr(sum)(zero)(list123) // => six
export const foldr = Y(r => f => a => xs => If(isNil(xs))(_ => a)(_ => f(head(xs))(r(f)(a)(tail(xs))))())
foldl
behaves the same as foldr
except it iterates across the list starting from the left and the reducing function takes the accumulator before the list element. The fact it can be defined in terms of foldr
is pretty awesome
foldl(sum)(zero)(list123) // => six
export const foldl = f => a => xs => foldr(x => g => y => g(f(y)(x)))(x => x)(xs)(a)
append
takes a value and a list then returns a list with the value appended
append(four)(list123) // => list of [one two three four]
export const append = x => xs => foldr(cons)(cons(x)(nil))(xs)
prepend
takes a value and a list then returns a list with the value prepended
prepend(zero)(list123) // => list of [zero one two three]
export const prepend = cons
drop
takes a numeral n and a list and returns a new list with all but the first n values
drop(two)(list123) // => list of [three]
export const drop = n => xs => n(tail)(xs)
slice
takes two numerals and returns a new list starting from the first index up to and excluding the second index
slice(one)(three)(list123) // => list of [two three]
slice(one)(two)(list123) // => list of [two]
export const slice = n => m => xs => take(pred(m))(drop(n)(xs))
take
takes a numeral n and a list and returns a new list of only the first n values
take(two)(list123) // => list of [one two]
export const take = n => foldl(acc => val => If(lt(length(acc))(n))(append(val)(acc))(acc))(nil)
concat
takes two lists and joins them together
concat(list123)(list123)
// => list of [one two three one two three]
export const concat = xs => ys => foldr(cons)(ys)(xs)
zip
takes two lists and returns a list where each value is a list of the correspondingly indexed values in the input lists. The returned list is the length of the shorter input lists
zip(list123)(list246)
// => list of lists [[1 2] [2 4] [3 6]]
export const zip = xs => ys => map(i => cons(nth(i)(xs))(cons(nth(i)(ys))(nil)))(range(zero)(pred(If(lt(length(xs))(length(ys)))(length(xs))(length(ys)))))
zipWith
takes a function and two lists and returns a list where each value is the value returned when the values in each of the given lists at the relevant index is applied to the supplied function. The returned list is the length of the shorter input lists
zipWith(add)(list123)(list246) // => list of [3 6 9]
export const zipWith = f => xs => ys => map(i => f(nth(i)(xs))(nth(i)(ys)))(range(zero)(pred(If(lt(length(xs))(length(ys)))(length(xs))(length(ys)))))
all
takes a predicate and a list and returns True
if every value applied with the predicate returns True
and returns False
otherwise
all(lt(zero))(list123) // => True
all(lt(three))(list123) // => False
export const all = f => foldl(a => b => and(a)(f(b)))(True)
export const last = foldl(a => b => b)(nil)
export const length = foldl(a => b => succ(a))(zero)
none
takes a predicate and a list and returns True
if every value applied with the predicate returns False
and returns True
otherwise
all(lt(five))(list123) // => False
all(lt(three))(list123) // => True
export const none = f => xs => not(all(f)(xs))
nth
takes a numeral n and a list and returns the value at index n
nth(zero)(list123) // => one
nth(two)(list123) // => three
export const nth = n => xs => head(n(tail)(xs))
export const sum = foldl(add)(zero)
filter
takes a predicate and a list and returns a list comprised only by those values for which the predicate returns True
filter(lt(two)(list123) // => list of [three]
export const filter = f => foldr(val => acc => If(f(val))(cons(val)(acc))(acc))(nil)
map
takes a function and a list and returns a new list with the function applied to every function in the given list
map(mult(two))(list123) // => list of [two four six]
export const map = f => foldr(val => acc => cons(f(val))(acc))(nil)
export const reverse = foldl(a => b => cons(b)(a))(nil)
reject
takes a predicate and a list and returns a list comprised only by those values for which the predicate returns False
reject(gte(two)(list123) // => list of [three]
export const reject = f => foldr(val => acc => If(f(val))(acc)(cons(val)(acc)))(nil)