A bit of functional fun

Tasking myself with writing a budget tracker, I have got back into a bit of functional programming. This may well bore the pants off you, but just thought I would share.

The following is a small functional library I put together last night along with some comments and examples. Could easily just use ramdajs, lodash or underscore, but it’s nice to play:)

// Functor will enable me to chain methods as well
// as inspect returned values along the way
const Functor = x => ({
    map: f => Functor(f(x)),
    inspect: () => (console.log(x), Functor(x)),
    fold: f => f(x)
})

// A simplified compose function that expects two functions 'f' and 'g' as arguments.
// The returned function expects a value 'x', which first of all will be passed to function 'g'.
// The returned value from function 'g' will then passed to function 'f' returning a final value.
//
// compose example
// const halve = (x) => x / 2
// const sqr = (x) => x * x
// const halveAndSquare = compose(sqr, halve) <- note order right to left
// const squareAndHalve = compose(halve, sqr)
//
// console.log(halveAndSquare(10)) -> 25
// console.log(squareAndHalve(10)) -> 50
const compose = (f, g) => (x) => f(g(x))

// values example
// values({x: 2, y: 5}) -> [2, 5]
const values = (obj) => Object.values(obj)

// Depending on the number of arguments supplied the following
// curried functions will return either a value or a function that expects a value

// multiply example
//
// const multiplyBy5 = multiply(5)
// [1, 2, 3].map(multiplyBy5) -> [5, 10, 15]
const multiply = (x, y) => (y !== undefined) ? x * y : (y) => x * y

// add example
//
// [1, 2, 3].reduce(add, 0) -> 6
const add = (x, y) => (y !== undefined) ? x + y : (y) => x + y


// map example
//
// const double = multiply(2)
// map(double, [1, 2, 3]) -> [2, 4, 6]
const map = (fn, array) => {
    const mapFn = (array) => array.map(fn)

    return (array !== undefined) ? mapFn(array) : mapFn
}

// map, reduce and compose example
//
// const double = multiply(2)
// const doubleAndSum = compose(reduce(add, 0), map(double))
// doubleAndSum([1, 2, 3]) -> 12
const reduce = (fn, init, array) => {
    const reduceFn = (array) => array.reduce(fn, init)

    return (array !== undefined) ? reduceFn(array) : reduceFn
}

// pick example
// pick([x, y], {x: 2, y: 3, z: 4}) -> {x: 2, y: 3}
//
// can also be curried
// const xy = pick(['x', 'y'])
// [{x: 2, y: 3, z: 4}, {x: 5, y: 6, z: 7}].map(xy) -> [{x: 2, y: 3}, {x: 5, y: 6}]
const pick = (keys, obj) => {

    const fn = (sourceObj) => {
        return keys.reduce((obj, key) => {
            obj[key] = sourceObj[key]
            return obj
        }, {})
    }

    return (obj !== undefined) ? fn(obj) : fn
}

A usage example for totaling up amounts. Transaction is either -1(expense) or 1(income)

// example of data acquired from form entries
const entries = () => new Map([
    ['kxw9vxyzh7kuk6lb7', { date: '2022-01-01', description: 'Budget', transaction: '1', amount: '2500' }],
    ['kxvniztqt7088wi5z6', { date: '2022-01-01', description: 'Car Insurance', transaction: '-1', amount: '38' }],
    ['kxvp7fcpvhbgd0fz6vg', { date: '2022-01-01', description: 'Broadband', transaction: '-1', amount: '37' }],
    ['kxw9vnobe37kuk6lb7', { date: '2022-01-01', description: 'Photoshop', transaction: '-1', amount: '12' }]
])

const formatCurrency = (locale, currency) => (amount) => (
    amount.toLocaleString(locale, {
        style: 'currency',
        currency: currency
    })
)

const updateTotal = (() => {

    const toSterling = formatCurrency('en-GB', 'GBP')

    const sumAmount = (sum, [x = 0, y = 1]) => add(sum, multiply(x, y))

    const getPairedValues = compose(values, pick(['transaction', 'amount']))

    return ({ sumTotal, cache }) => {

        const entries = Array.from(cache.values())

        const total = Functor(entries)
            .map(map(getPairedValues))
            .inspect() // [ [ '1', '2500' ], [ '-1', '32' ], [ '-1', '37' ], [ '-1', '12' ] ]
            .map(reduce(sumAmount, 0))
            .inspect() // 2419
            .fold(toSterling)

        sumTotal.textContent = total // £2,419.00
    }
})()

updateTotal({
    sumTotal: {}, // HTMLElement somewhere
    cache: entries()
})

It is maybe over engineered, vanilla JS with destructuring would/does provide a simpler/quicker alternative.

A good exercise though. I like the declarative aspect to it and that with using a functor I am not limited to specific chained methods like for instance with Array. I also like that I can use inspect to monitor the outputs. Another viable alternative would be to use pipe

codepen here

Any feedback, advice appreciated:D

2 Likes

I was in the process of watching a video on ‘lenses’, and was intrigued by his implementation of a curry function. I say ‘intrigued’, I mean ‘stumped’.

This is the function

const curry = (f, arity = f.length, ...args) => (
  arity <= args.length
    ? f(...args)
    : (...argz) => curry(f, arity, ...args, ...argz)
)

To start off, functions have a length property. The length property is equal to the number of parameters or the arity.

So for example

function myFunc(x, y, z) { /* do something */ }

console.dir(myFunc)
/*
ƒ myFunc(x, y, z)
  arguments: null
  caller: null
  length: 3 <- 3 parameters (x, y, z)
  name: "myFunc"
*/

I had to do a bit of logging to try and get my head around the curry function. I’m sure there are ‘rainmen’ out their who can just see the logic at first glance — not me!!

const curry = (f, arity = f.length, ...args) => {
  console.log('function: ', f)
  console.log('function arguments: ', arity)
  console.log('supplied arguments: ', ...args)

  return arity <= args.length
    ? f(...args)
    : (...argz) => curry(f, arity, ...args, ...argz)
}

and tests

// a simple function to be curried
const prop = (key, obj) => obj[key]

const curryProp = curry(prop)
/* Logged Outputs
function:  (key, obj) => obj[key]
function arguments:  2
supplied arguments: // none
*/

arity <= args.length // false
// note: f, arity and ...args will be store in a closure
// no args at this point
return (...argz) => curry(f, arity, ...args, ...argz)

next

const keyA = curryProp('a')
/* Logged Outputs
function:  (key, obj) => obj[key]
function arguments:  2
supplied arguments:  'a' // 1
*/

arity <= args.length // false
// args is 'a'
return (...argz) => curry(f, arity, ...args, ...argz)

finally

keyA({a: 5})
/*
function:  (key, obj) => obj[key]
function arguments:  2
supplied arguments:  'a', {a: 5} // 2
*/

arity <= args.length // true
return f(...args) // call (key, obj) => obj[key] with supplied arguments and return

So to boil it down, until the point that the curry function receives the final argument it returns (...argz) => curry(f, arity, ...args, ...argz). This function keeps a store of the original given function, it’s number of parameters and a store of arguments supplied to the curryied function so far. Does that make sense?

When the final piece of the puzzle is supplied, in the case of our prop function the ‘obj’ argument it will then call that function and return the value.

Now taking the previous post, and ‘multiply’ as an example it can be refactored like so.

const multiply = (x, y) => (y !== undefined) ? x * y : (y) => x * y

// refactored to 

const multiply = curry((x, y) => x * y)

and testing

console.log(multiply(5, 10)) // 50
console.log(multiply(5)(10)) // 50

console.log([1,2,3,4].map(multiply(5))) // [5,10,15,20]

I have to do this stuff to try and cement the knowledge into my brain. I hope it is of at least some interest.

3 Likes

In functional languages such as Haskell this is all there is.

Take for example

sum :: Integer → Integer → Integer
sum a b = a + b

At first glance it would seem this is a function that takes two arguments. It’s not. It’s a function that takes one argument and returns another function that takes one argument.

So for example I can create a new function like so

sum5 = sum 5

This is a new function that adds 5 to whatever argument you supply it.

No need for the curry function, this is built into the language. Sure, you can still call sum with two arguments, but that’s really syntactic sugar :slightly_smiling_face:

This is how you can inject services like database into functions without the caller of the resulting function knowing what database supplied or indeed without having to be aware a database was supplied at all.

Functional programming is a bit harder to get your head around since imperative programming feels more “natural”, but it’s so nice!

1 Like

I suspect I might have to bite the bullet and venture into Haskell. I have seen a few Haskell related posts on stackoverflow and have to confess, a bit over my head.

I’m very much in the shallow end when it come to functional programming, and it is a real mental workout, but yes ‘so nice’ — very much hooked:)

1 Like

Learning to manipulate streams can aid in understanding functional programming. Libraries like RxJs are functional in nature and require embracing functional concepts to create elegant solutions to problems.

https://blog.briebug.com/blog/using-rxjs-to-write-functional-javascript

1 Like

Warning! A bit of a lengthy one.

assocPath

Functional Setter

If you google online for ‘JS setting nested properties’ you can find various implementations.

I wanted to focus on writing something similar, that would form the setting method for the functional getter/setter lensPath. Following functional programmings non-mutational approach, one key feature to assocPath is that it returns a shallow clone of the source object — leaving the source or store object intact.

A good place to start would be a usage example with lenses.

const user = {
    id: 1,
    personalInfo: {
        name: 'Robert',
        address: { city: 'Timbuktu' }
    }
}

// create a lens focusing on the nested name property
const nameLens = lensPath(['personalInfo', 'name'])

// Under the hood the assocPath method comes into play
const updatedUser = set(nameLens, 'Bob', user)

updatedUser.personalInfo.name // -> Bob

// The source object remains intact
user.personalInfo.name // -> Robert

Recursion

Following ramdaJS’s approach I wanted to use recursion. Breaking problems down recursively is still something of an enigma to me, and albeit I have a grasp of the ‘how it works’, it is somewhat a trial and error exercise.

So starting simple I wrote a function that given a path builds nested objects.

const buildObjectTree = function buildTree([key, ...restOfKeys], value, source = {}) {

    // recursively call buildTree until we are down to one key
    source[key] = (restOfKeys.length)
      ? buildTree([restOfKeys], value, {})
      : value

    return source
}

// parameters (path, value, source)
buildObjectTree(['a', 'b', 'c'], 50, {}) // -> {a: {b: {c: 50}}}

Our base condition is reaching the last key of the path, in this example ‘c’ at which point we assign the value 50 and return {c: 50} and so on {b: {c: 50}} -> {a: {b: {c: 50}}}

Objects and Arrays

Moving on to the next step, assocPath also works with arrays. If we feed a number into the path, it is taken as an index rather than a key.

buildObjectTree(['a', 'b', 0], 'first index', {}) // -> {a: {b: ['first index']}}

With a simple additional check using isNaN we can figure out if the next key is an array index or an object key and pass the correct object type to our next recursive call.

const buildObjectsTree = function buildTree(
    [key, nextKey, ...restOfKeys], value, source = {}
) {

    source[key] = (nextKey !== undefined)
        ? buildTree(
              [nextKey, ...restOfKeys],
              value,
              // is nextKey not a number?
              // if so pass an object, otherwise an array
              isNaN(nextKey) ? {} : []
          )
        : value

    return source
}

buildObjectsTree(['a', 0, 'c'], 50, {}) // -> { a: [{c: 50}] }

Merging with source data

So far we have been working with empty objects, but we want to be able to set properties on existing data, either over-writing or creating entirely new properties.

We can see here with the current function, that we are completely over-writing the exisitng object and in the process losing the ‘d’ property.

buildObjectsTree(['a', 'b', 'c'], 48, { a: { d: 50 } }) // { a: { b: { c: 48 } } }

We can fix this by checking if a key already exists with hasOwnProperty and pass the existing property instead of a new empty object.

Object.hasOwnProperty.call(object, key) is a tad verbose, so a helper function would be useful.

const hasOwn = (obj, key) => Object.hasOwnProperty.call(obj, key)
const buildTreeMerge = function buildTree(
    [key, nextKey, ...restOfKeys], value, source = {}
) {

    source[key] = (nextKey !== undefined)
        ? buildTree(
              [nextKey, ...restOfKeys],
              value,
              (hasOwn(source, key))
                  ? source[key] // already exists pass this instead
                  : isNaN(nextKey) ? {} : []
          )
        : value

    return source
}

// now 'd' remains intact
buildTreeMerge(['a', 'b', 'c'], 48, { a: { d: 50 } })
// -> { a: { d: 50, 'b': { c: 48 } } }

Mutation

As per the introduction we don’t want to mutate the source object and the current implementation fails.

const sourceObj = { a: 52 }

const updatedObject = buildTreeMerge(['a', 'b', 'c'], 50, sourceObj)
updatedObject // -> { a: { b: { c: 48 } } }
sourceObj // -> { a: { b: { c: 48 } } } <- No good!!

The solution to this, is instead of directly mutating the source object e.g. source[key] = ... we clone the source first, mutate it and return that instead.

Clone and merging

Interesting we can use Object.assign with a target array and an array-like object to create a new array.

Object.assign([], { 0: 'first index', 1: 'second index' })
/*
Array(2)
  0: "first index"
  1: "second index"
  length: 2
*/

So if we want to clone our source, which maybe an array or an object, and mutate it, this will do the trick!

Object.assign(
    Array.isArray(source) ? [] : {}, // target
    source, // clone the source
    { [key]: value } // add the new index or key with value
)

Final assocPath

Let’s create a merge function first

const mergeObjects = (key, value, source) => (
    Object.assign(
        Array.isArray(source) ? [] : {}, source, { [key]: value }
    )
)

Final function

const assocPath = function buildTree(
  [key, nextKey, ...restOfKeys], value, source = {}
) {

    // assign to a temporary variable
    const newValue = (nextKey !== undefined)
        ? buildTree(
              [nextKey, ...restOfKeys],
              value,
              (hasOwn(source, key))
                  ? source[key]
                  : isNaN(nextKey) ? {} : []
          )
        : value

    // return mutated clone
    return mergeObjects(key, newValue, source)
}

Quick test

const user = {
    id: 1,
    personalInfo: {
        name: 'Robert',
        address: { city: 'Timbuktu' }
    }
}

const updatedUser = assocPath(['personalInfo', 'name'], 'Bob', user)
updatedUser.personInfo.name // -> 'Bob'
user.personInfo.name // -> 'Robert'

Conclustion

It needs some proper testing, but albeit probably not the best solution, it appears to work :slight_smile:

The next stage will be to write a nested getter function, which should be a bit more straight forward.

2 Likes

What is the difference between this and using a state store like redux.

That video is from nearly half a decade ago. Surely there have been advancements since then.

This is just a bit of a programming and learning exercise — A look under the hood.

As I say I don’t know redux, maybe someone here who is more experienced in both functional programming and redux can give you a better answer.

Video deleted.

Shame. I’ve been following this thread and would have checked it out.

1 Like

Sorry James, here you go.

1 Like

The last commit to shades js was over 3 years. Probably not something worth investing time with learning.

Another look at Currying and a Gotcha.

Reading through Mastering Functional Programming in Javascript I was looking at another implementation of curry using function’s bind method.

const curry = (fn) =>
  fn.length === 0 ? fn() : x => curry(fn.bind(null, x))

Bind returns a function with fixed arguments changing the length property accordingly.

const fn1 = (x, y, z) => x + y + z
fn1.length // -> 3
const fn2 = fn1.bind(null, 5)
fn2.length // -> 2
const fn3 = fn2.bind(null, 10)
fn3.length // -> 1

The above curry function is basically a recursive function with the difference being the recursive call is wrapped inside of a returned function. So you end up with a kind of manual recursion where by you invoke the returned functions up untill the base condition fn.length === 0 is met.

At this point the function which now has all arguments bound is invoked — nice!

Default parameters gotcha

This is where I ran into an issue, best illustrated with some code

const sum = curry((x, y = 10) => x + y)
const add5 = sum(5)
add5(20) // -> Uncaught TypeError: add5 is not a function

This error is also thrown with ramdaJs’s curry.

If we look at MDN’s Function.length page, we get an answer as to why this happens.

console.log((function(a, b = 1, c) {}).length)
// 1, only parameters before the first one with
// a default value is counted

Passing an arity argument

The curry function from the earlier post does almost have us covered. Here it is again to save scrolling.

const curry = (f, arity = f.length, ...args) => (
  arity <= args.length
    ? f(...args)
    : (...argz) => curry(f, arity, ...args, ...argz)
)

We can pass a second argument which will fix the number of arguments.

const sum = curry((x, y = 10) => x + y, 2) // fixed to 2 arguments
const add5 = sum(5)
add5(20) // -> 25 Good!

However when we drop the argument in our call to add5, due to this condition arity <= args.length, instead of the default parameter being used the second function is returned instead.

const add5 = sum(5)
add5() // -> (...argz) => curry(f, arity, ...args, ...argz) Bad!

RamdaJS’s curryN appears to run into the same problem.

const sum = R.curryN(2, (x, y = 10) => x + y) // fixed to 2 arguments

const add5 = sum(5)
add5() // -> returns a function

Solution

Well hopefully anyway.

In the aforementioned Mastering Functional Programming book, there is a solution to working with variadic functions which works equally well with default parameters.

const curryN = (fn, arity = 1) =>
  arity === 0
    ? fn()
    : x => curry(fn.bind(null, x), arity - 1) // decrement arity

It appears that bind does carry across the default parameter functionality, and this time we’re good.

const sum = curryN((x, y = 10, z) => x + y + z, 3) // Fixed to 3 arguments

const add5 = sum(5)
const add5andDefault = add5() // default of 10
add5andDefault(10) // -> 25

Conclusion

Prior to this I had considered calling toString on the given function, and regex matching arguments to get the arity, but I think this fixed arity solution will suffice.

I will post a getByPath function breakdown next. It was only on running a curry test with it, that I ran into this issue and thought it was worth looking into.

I came across this article and thought it would be relevant to this discussion. Particularly regarding the organization of functions into modules using mjs files. Even using unbundled module files for small applications instead of classic scripts.

1 Like

I was actually in the process of trying out some markdown to html parsers, and it became apparent that my deepclone post maybe needed a bit of a rewrite — way to keep focused on the task in hand :grin:

deepClone

To avoid mutating, an important part of functional programming is cloning. If we want to be thorough with this process and not share referenced nested properties with the source object, a shallow copy isn’t going to suffice.

The solution to this is deep cloning and some useful reading on the topic came in the form of a response posted on stackoverflow How do I correctly clone a JavaScript object? and MDN’s page on Object.assign.

Property Descriptors

The part of MDN’s page that was of particular interest to me was a polyfill for Copying accessors. With vanilla Object.assign instead of copying accessor functions to the target object, the evaluated value of that function is copied instead.

const source = {
    x: 10,
    y: 5,
    get sumXY() { return x + y }
}

const clone = Object.assign({}, source)

console.dir(clone)

Object
    sumXY: 15 // -> evaluated instead
    x: 10
    y: 5

The solution to this and for copying symbols is to use Object.getOwnPropertyDescriptors and Object.defineProperties.

// Note: As per Object.assign this makes a shallow copy
const clone = Object.defineProperties(
    {}, Object.getOwnPropertyDescriptors(source)
)

console.dir(clone)

Object
    x: 10
    y: 5
    get sumXY: ƒ sumXY() // -> more like it!

Nested properties

Nested properties will come in the form of objects or arrays. If we come across one or the other we will want to pass that property to a recursive call of deepclone so that we can run through it’s contents. Likewise if those contents contain either of these types we will want to repeat this recursive process.

As we are cloning and not just reading properties, on each successive nested recursive call we will want to return a shallow copy, which is where the above defineProperties and getOwnDescriptors comes in.

Given that defineProperties first argument requires a target object, we will also need to know if the passed object is an array or an object so that we can clone to the correct object type.

We could use a ternary, but another solution is to call the Object.prototype.constructor of the passed object.

const obj = {a: 1, b: 2}
const arr = [1, 2, 3]

arr.constructor() // -> [] empty array
obj.constructor() // -> {} empty object

The previous clone solution can now be amended like so

const clone = Object.defineProperties(
    source.constructor(), // -> [] or {}
    Object.getOwnPropertyDescriptors(source)
)

Final solution

To start with a bit of setup to remove some of the verbose ugliness of methods like Object.getOwnPropertyDescriptors.

const toString = obj => {}.toString.call(obj)
const isArray = obj => Array.isArray(obj)
const isObject = obj => toString(obj) === '[object Object]'

const getOwnDescriptors = obj => Object.getOwnPropertyDescriptors(obj)
const defineProps = (...args) => Object.defineProperties(...args)

and our deepClone function

const deepClone = function deepClone (source) {

    // shallow clone the source
    const clone = defineProps(source.constructor(), getOwnDescriptors(source))

    // run through it's contents
    for (const [key, prop] of Object.entries(clone)) {

        // If we have a nested property, pass it to deepClone and repeat.
        if (isArray(prop) || isObject(prop)) clone[key] = deepClone(prop)
    }

    return clone
}

Basic test

source object

const source = {
    x: 5,
    y: 10,
    z: { a: [1, 2, 3] },
    date: new Date(),
    get sum () { return this.x + this.y },
    getA () { return this.z.a }, // return nested property

    [Symbol.iterator]: function * () {
        yield 1
        yield 2
        yield 3
    }
}

tests

const clone = deepClone(source)

clone.x = 10 // change the value on clone
clone.sum  //-> 20
source.sum //-> 15 source unchanged

clone.z.a.push(4) // push value into nested array in clone
clone.getA()  //-> [1, 2, 3, 4]
source.getA() //-> [1, 2, 3] source unchanged

Object.getOwnPropertySymbols(clone) //-> [ Symbol(Symbol.iterator) ]
[...clone] //-> [1, 2, 3] // symbol iterator copied

typeof clone.date //-> object - not converted to string

Conclusion

In most use cases I’m sure we will only be dealing with objects, arrays and primitive types like numbers and strings. Catering for edge cases like symbols and accessors is maybe over the top, but given it requires one line of code I think it would be silly not to take advantage.

edit: I just want to add that deepClone is an integral part of getByPath. If the target of the path is a nested property we will want to return a clone of that. So this should tie in.