Implementing some utilities.

I’ve been teaching programming to friends and acquaintances for as long as I have been able to. Seeing someone else learn and enjoy something that you taught them is a wonderful feeling, and helps to concretize your knowledge in the same way that writing does.

Being new to programming, eventually you start recognizing the same patterns and functions popping up in a number of places. It dawns on you that many problems could be solved by aggregating a set of these tools to create new ones.

Learning which these functions are is a big step in becoming more proficient at programming. Knowing when not to use them equally so[0].

A great step in learning more about them is to try and implement one whenever you first hear about it.

Here are some of the more common examples that tend to come up in a lot of languages, especially those with a functional nature about them.




Note: Implementations are in JavaScript as it's the programming lingua franca of the web, deliberately not reusing the equivalent functions that already exist in the language.

Despite their functional nature, these implementations rely on mutating existing references.

Some of these could be member functions on collections or boxes to enable chaining.



Map.

const map = (array, f) => {
    for (let i = 0; i < array.length; i++) {
            array[i] = f(array[i])
    }
    return array
}

Good ole' map! Takes a array and a function, returns the array with all elements ran through (and replaced with the return from) the provided function.

This implementation utilizes the fact that JS arrays can hold arbitrarly values, saving the need for creating a new array of the correct output type.

Example.

const increment = n => n + 1

$ map([1, 2, 3], increment)
> Array(3) [ 2, 3, 4 ]

Filter.

const filter = (array, f) => {
    let n = 0
    for (let i = 0; i < array.length; i++) {
        if (f(array[i])) {
                array[n] = array[i]
                n++
        }
    }
    return array.slice(0, n)
}

Removes all elements that don’t match the provided predicate function.

Mutates the array in place, slicing off the end. No new array needed.

If you’re worried about mutating previous references, creating a new array with the same length works equally well, with one added allocation.

Example.

const positiveNumber = n => n > 0

$ filter([-1, 0, 1, 2, 3], positiveNumber)
> Array (2) [ 1, 2, 3 ]

Replace.

const replace = (array, predicate, f) =>
    map(array, x => predicate(x) ? f(x) : x)

Replaces an item in an array matched by predicate.

Example.

const isZero = n => n == 0

$ replace([0, 2, 3], isZero, increment)
> Array (3) [ 1, 2, 3 ]

First.

const first = (array, predicate) =>
    filter(array, predicate)[0]

Filters an array and returns its first element.

Works great if implemented as an expression, throws on null otherwise.

Example.

$ first([1, 2, 3], isOne)
> 0

$ first([1, 2, 3], isFour)
> undefined

Or.

const or = (item, or) => item ? item : or

For situations where null/nil/undefined is undesirable.

Example.

const personOrDefault = (users, name, defaultPerson) => 
    or(first(users, p => p.name == name), 
       defaultPerson)

const people = [
    { name: 'Berta' },
    { name: 'NotSteve' }
]

const defaultPerson = { name: 'Default' }

$ personOrDefault(people, 'Berta', defaultPerson)
> Object { name: 'Berta' }

$ personOrDefault(people, 'Steve', defaultPerson)
> Object { name: 'Default' }

SetProperty.

const setProperty = (object, property, value) => {
    return { ...object, [property]: value }
}

Returns an object with one property modified.

Necessary for things like mapping an array of objects.

Example.

const yourNameIsSteve = obj =>
    setProperty(obj, 'name', 'Steve')

const everyoneIsNamedSteve = people =>
    map(people, yourNameIsSteve)

$ yourNameIsSteve(first(people))
> Object { name: 'Steve' }

$ everyoneIsNamedSteve(people)
> Array (2) [ Object { name: 'Steve' }, Object { name: 'Steve' }]

All.

const all = (array, predicate) =>
    filter(array, predicate).length == array.length

Returns true if predicate matches the entire array.

Example.

$ all([1, 2, 3], n => n == 2)
> false

$ all([1, 2, 3], n => n > 0)
> true

Any.

const any = (array, predicate) =>
    find(array, predicate) != null

Returns true if predicate matches one item in array.

Example.

$ any([1, 2, 3], n => n == 0)
> false

$ any([1, 2, 3], n = > n == 2)
> true

Append.

const append = (array, item) =>
    array ? [...array, item] : [item]

Returns the array with item appended.

Example.

$ append([1, 2, 3], 4)
> Array (4) [ 1, 2, 3, 4 ]

Identity.

const identity = a => a

Shorthand for evaluating booleans. Works great in conjunction with truthiness.

Example.

$ [true, false].filter(identity)
> Array (1) [ true ]

Example with function chaining.

const hasErrors = items => 
    items.map(i => Object.values(i.errors))
         .filter(identity)
         .length > 0

const items = [ { price: 50,
                  errors: { priceIsNull: false } },
                { price: null,
                  errors: { priceIsNull: true } } ]

$ hasErrors(items)
> true


[0] It can be tempting to solve all problems this way. Despite the brevity and aesthetic beauty of expressing some problems in this manner, sometimes the cost isn't worth it (as hand written implementations might be both clearer and more performant).

The design patterns craze of the 90s and early 00s suffered from this problem, as some interpeted it as an attempt at standardizing programming patterns to the point where solving any a problem “by hand” was considered an anti-pattern.