3

I am trying to refactor some JavaScript code to use functional programming principles.

I have some functions that I want to use in a series of maps.

const transformedData = rawData
 .map(getIdFromRawData)
 .map(getCachedDataById)
 .map(transformData)

getIdFromRawData takes in rawData:

{
 id: 123,
 name: "taco",
 is_tasty: true
}

And returns 123.

getCachedDataById takes in an id like 123 and returns cachedData (it's an impure function that gets the data with an I/O operation):

{
 transformedValue1: "cheese",
 transformedValue2: "taco mix",
 transformedValue3: "salsa",
 ...
}

The final function in the pipeline, transformData, needs to take in both cachedData and rawData:

function transformData({ cachedData, rawData }) {
 if (Object.keys(cachedData).length) {
 // use cachedData to skip some work while transforming
 } else {
 // use rawData for transformation
 }
}

But I didn't pass along rawData in the return value of getIdFromRawData. I didn't do that for the following reasons:

  • getIdFromRawData is used elsewhere and needs to have a generic signature.
  • easier to test getIdFromRawData
  • function name getIdFromRawData does what it says on the tin

I want to have some kind of "pass through" functionality with my pipeline. In researching how best to handle this, I've seen mention of using a reader monad, but I'm not sure how to implement it for this use case, or if it's even the right approach.

Something like this initially comes to mind:

function passThrough({ data, func, key }) {
 const result = func(data[key])
 return { ...data, ...result }
}
...
const transformedData = rawData
 .map((data) => passThrough({ data, func: getIdFromRawData, key: 'id' }))
 .map(getCachedDataById)
 .map(transformData)

This doesn't quite do what I want, though, because now getCachedDataById is receiving an argument shaped like an object ({ ... }) and not a number (123).

rawData needs to be present and accessible throughout the pipeline, but it only is relevant at certain parts of the pipeline.

Is there a certain kind of monad that applies to this?

Are monads not really relevant here?

Is there another approach for this?

asked Jun 6, 2024 at 19:36
2
  • 1
    Your function passthrough is touching on the category theory concept of arrows (haskell.org/arrows) specifically the first function. Commented Jun 7, 2024 at 14:16
  • This is really interesting -- thanks. I'm still unclear on what exactly a monad is, and how that differs from this arrow. I'm really debating learning a new language to bolster my understanding of functional programming. Maybe it should be Haskell?! Commented Jun 8, 2024 at 13:14

3 Answers 3

3

You can use Lambdas and Anonymous types.

const transformedData = rawData
 .map(i => (id: getIdFromRawData(i), raw: i))
 .map(i => (cachedData: getCachedDataById(i.id), raw: i.raw))
 .map(i => transformData(i.cachedData, i.raw))

Now I'm not constrained by the parameters and return type of my functions as the lambda function and anon type allow me to wrap the underlying function with another function without having to define it.

*I dont always use i as an iteration variable, but when i dont it's a j/k

answered Jun 6, 2024 at 20:42
1
  • This seems so obvious and yet I couldn't figure out on my own. Thank you Commented Jun 6, 2024 at 21:51
3

Instead of just chaining map, would it make sense to do something like this?

const ids = rawData.map(getIdFromRawData);
const cachedData = ids.map(getCachedDataById);
const transformedData = ids.zip(cachedData).map(transformData);
answered Jun 6, 2024 at 19:58
3

Usually in functional languages you would use something like do notation in this situation. However, remember you can always combine maps, like (forgive my syntax):

const transformedData = rawData.map(raw => {
 const id = getIdFromRawData(raw);
 const cached = getCachedDataById(id);
 transformData(raw, cached);
})
answered Jun 8, 2024 at 16:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.