This is a little project that I wrote in a couple of hours as a proof of concept. This project pretends to prove that is possible to recreate some of the most important methods in the Array.prototype
object by just using Functional Programming
concepts such as:
Pure Functions
Recursion
The Head and Tail Pattern
Immutability
Accumulators
Pattern Matching
(Array Destructuring
was the closest thing to Pattern Matching in JavaScript).
I did not use any of the following built in features of JavaScript:
- Control Flow Constructs (
if..elseif..else
,switch
,for
,while
,do..while
,try..catch..final
, etc) - Mutable Variables (
var
,let
) - Object Oriented Programming Techniques (classes, prototypes, plain objects, etc)
Array.prototype
Methods (Obviously! With the exception ofArray.prototype.isArray
)
These are the functions that I decided to implement for this exercise:
Returns the head or first element of the given list. Not available in Array.prototype
.
export const hd = ([head, ..._]) => head
Returns the tail of the given list. Not available in Array.prototype
.
export const tl = ([_, ...tail]) => tail
Returns the length of the given list.
export const length = list => _length(list, 0)
const _length = ([_, ...tail], acc) =>
![_, ...tail][0] ? acc : _length(tail, acc + 1)
I know, this is a very dumb function ¯\_(ツ)_/¯
export const is_list = list => Array.isArray(list)
export const reduce = ([head, ...tail], acc = 0, fun) =>
!length([head, ...tail]) ? acc : reduce(tail, fun(acc, head), fun)
// Using our custom reduce function
export const map = (list, fun) =>
reduce(list, [], (acc, val) => [...acc, fun(val)])
// Using a helper function, recursion and an accumulator
export const map2 = (list, fun) => _map2(list, fun, [])
const _map2 = ([head, ...tail], fun, acc) =>
!length([head, ...tail]) ? acc : _map2(tail, fun, [...acc, fun(head)])
// Another solution
export const map3 = ([head, ...tail], fun) =>
!length([head, ...tail]) ? [] : [fun(head), ...map3(tail, fun)]
// Using our custom reduce function
export const filter = (list, fun) =>
reduce(list, [], (acc, val) => fun(hd(list)) ? [...acc, hd(list)] : acc)
// Using a helper function, recursion and an accumulator
export const filter2 = (list, fun) => _filter2(list, fun, [])
const _filter2 = ([head, ...tail], fun, acc) =>
!length([head, ...tail]) ? acc : _filter2(tail, fun, fun(head) ? [...acc, head] : acc)
// Another solution
export const filter3 = ([head, ...tail], fun) =>
!length([head, ...tail]) ? [] : (
fun(head) ? [head, ...filter3(tail, fun)] : filter3(tail, fun)
)
export const foreach = ([head, ...tail], fun) =>
!length([head, ...tail]) ? undefined : (
fun(head) ? undefined : foreach(tail, fun)
)
export const reverse = ([head, ...tail]) =>
!length([head, ...tail]) ? [] : [...reverse(tail), head]
export const some = ([head, ...tail], fun) =>
!length([head, ...tail]) ? false : (
fun(head) ? true : some(tail, fun)
)
export const every = ([head, ...tail], fun) =>
!length([head, ...tail]) ? true : (
fun(head) ? every(tail, fun) : false
)
export const flatten = ([head, ...tail]) =>
!length([head, ...tail]) ? [] : (
is_list(head)
? [...flatten(head), ...flatten(tail)]
: [head, ...flatten(tail)]
)
export const concat = (list, ...values) => [...list, ...flatten(values)]
export const fill = (list, val, start = 0, end = length(list)) =>
_fill(list, val, start, end, 0)
const _fill = ([head, ...tail], val, start, end, index) =>
!length([head, ...tail]) ? [] : (
index >= start && index <= end
? [val , ..._fill(tail, val, start, end, index + 1)]
: [head, ..._fill(tail, val, start, end - 1, index + 1)]
)
export const join = (list, sep) =>
reduce(list, "", (acc, val) => !acc ? `${val}` : `${acc}${sep}${val}`)
export const to_string = list => join(list, ",")
export const find = ([head, ...tail], fun) =>
!length([head, ...tail]) ? undefined : (
fun(head) ? head : find(tail, fun)
)
Not available in Array.prototype
, but still very useful to create lists of consecutive numbers.
export const range = (start, finish) => _range(start, finish, [])
const _range = (start, finish, acc) =>
start > finish ? acc : _range(start + 1, finish, [...acc, start])