Use chained method calls with Ramda

In notebook:
Work Notes
Created at:
2019-02-25
Updated:
2019-02-26
Tags:

Sometimes you are working with an API, that chains method together. For example $('.foo').child('.bar').attr('class')

The example is where Firebase returns storageRef object, that has a .child(filename) method.

firebase.storage().ref().child('foo').putString('bar')

In imperative style you would have this function:

  const createFileRef = (id, sref) => {
  var filename = createFileName(id)
  return sref.child(filename)
}

With Ramda this would look like this:

  const createFileRef = R.useWith(
  R.invoker(1, 'child'),
  [
    createFileName,
    R.identity
  ]
)

Or uncurry the compose function:

  const createFileRef = R.uncurryN(
  2,
  R.compose(
    R.invoker(1, 'child'),
    createFileName
  )
)

Is the fp code better? If you add a Maybe wrapper, then maybe yes...

Just for comparison, the functional code:

  const createFileName = R.concat(R.__, '/index.html')

const createFileRef = R.uncurryN(
  2,
  R.compose(
    R.invoker(1, 'child'),
    createFileName
  )
)

const putContents = R.flip(R.invoker(1, 'putString'))

const storeContents = R.uncurryN(
  3,
  R.compose(
    putContents,
    createFileRef
  )
)

Is the equivalent of:

  const createFileName = R.concat(R.__, '/index.html')

const storeContents = (fileid, storageRef, contents) => storageRef.child(createFileName(fileid)).putString(contents)

I'm not sure, that the functional style code would be any better (more readable or maintainable)

One argument could be that if you look at the individual elements of the program, those are easy to understand:

R.compose(putContents, createFileRef)

The rest (uncurryN) is just boilerplate so that you can have a classical style call signature: storeContents('myfile', storageRef, 'my contents'). In most cases, if you are using functional programming, you may not end up calling storeContents with all three arguments at the same time. Instead you would "build up" your call through several steps or pass the arguments as a tuple.

By the way the Jest mock setup for this:

  const firebase = require('firebase')
jest.mock('firebase')

const mockPutString = jest.fn()
mockPutString.mockReturnValue(Promise.resolve('inserted'))
const mockChildRef = jest.fn()
mockChildRef.mockReturnValue({
  putString: mockPutString
})
const storageRef = {
  child: mockChildRef
}