1

I'm trying to write a function composition that partially applies an argument at each step and ends up calling a curried two-argument function.

There is a set of example functions to compose. I removed the calculations that there are supposed to do as they are not relevant to the problem but let's assume that every argument is required.

const getDayLimit = () => {
  return 10
}

const getIpCount = ip => dayLimit => {
  return 99
}

const getIp = (deviceId, headerIp) => {
  // TODO: use Result monad to handle errors
  if (!deviceId && !headerIp) {
    throw new Error('Ip not provided')
  }
  return deviceId || headerIp
}

And the composition attempt:

const validateIp = R.compose(
  f => f(getDayLimit()),
  getIpCount,
  getIp
)

validateIp(1, 2)

In the first step, getIp received two values and based on them returns an ip that is then partially applied to getIpCount, now the composition return a function that expects the dayLimit argument that needs to be computed first.

The plain way of doing this could be: f => f(getAccountLimit()). I'd like to remove such function creation f => f... and pass it point-free.

Here's a helper function that solves this but is not handling all cases such as passing arguments to the result function:

const applyResult = result => f => R.compose(f, result)()

then I could do:

const result = R.compose(
  applyResult(getDayLimit),
  getIpCount,
  getIp
)

It seems too hacky for me and not substantial for my further use. I'd rather avoid writing my own helper function for this kind of problem.

Is there a functional way of computing arguments before partially applying them to a function? It seems to be a pretty common case in my mind, though perhaps I'm not thinking about the problem correctly.

Is my thinking incorrect about this problem and function composition?

What is a good approach to handling such a case with a function with two parameters in a composition?

Can this case of partially applying function arguments with each step be handled in a function composition?

Thank you!

tssr
  • 121
  • 3
  • 5
  • 2
    This seems an unusual requirement to me, but if you want it, I would suggest just using something like `const applyResult = g => f => f (g ())`. Functional programming has little use for zero-argument functions, but you may find something helpful involving [`R.thunkify`](https://ramdajs.com/docs/#thunkify). – Scott Sauyet Mar 29 '21 at 18:14
  • @ScottSauyet Thanks for the reply. I must be approaching function composition incorrectly then. In general, my problem is to compose `getIpCount` that accepts two parameters `ip` and `dayLimit`, both parameters need to be calculated using functions `getIp(deviceId, headerIp)` and `getDayLimit()`. In this example, `getDayLimit` doesn't take any arguments because it's supposed to read a configuration from a database. In some other case, I might need an argument there therefore I'm looking for a 'universal' solution for such a composition. What would be a good functional approach to this problem? – tssr Mar 29 '21 at 19:19
  • @ScottSauyet At the same time, if calculating the first parameter `ip` failed I wouldn't want `getDayLimit` to be run at all as it would call database for no reason. That's why I've been trying to compose it like that. – tssr Mar 29 '21 at 19:22
  • 1
    Ah, then perhaps I misunderstood. You might want to look at [`converge`](https://ramdajs.com/docs/#converge) or possibly [`lift`](https://ramdajs.com/docs/#lift), or -- hopefully not -- [`useWith`](https://ramdajs.com/docs/#useWith). – Scott Sauyet Mar 29 '21 at 19:52
  • 1
    `useWith(f, [g, h]) //~> (x, y) => f (g(x), h(y))`, `converge(f, [g, h]) //~> (x, y) => f (g (x, y), h(x, y))`, and `lift` is similar to `converge`, more general in some ways and more restricted in others. – Scott Sauyet Mar 29 '21 at 19:55
  • @ScottSauyet Ah, I remember getting it to work using converge but when I used [Folktale's `Result`](https://folktale.origamitower.com/api/v2.1.0/en/folktale.result.html) to handle errors I couldn't `R.chain` `converge` to not continue if there's an error in any of those arguments. Only managed to get it to work on the first argument, but it's getting out of the scope of the question I think. Perhaps I should create a new question for that case. – tssr Mar 29 '21 at 22:00

1 Answers1

2

I think I would use a continuation which, as I understand it, represents a computation that has been interrupted:

const cont = x => f => f(x);

With a continuation, you get x before f. Instead of doing f(x) you do cont(x)(f) which behind the scene just does f(x) for you.

At the time of composing the functions together you already know the value of x which is getDayLimit(), you just don't know the value of f yet which is known only when result is applied to the first two initial parameters.

So here's what I'd do:

const result = R.compose( cont(getDayLimit())
                        , getIpCount
                        , getIp);

Is there a functional way of computing arguments before partially applying them to a function?

I would simply note that you apply a function to a value (not the other way round)

customcommander
  • 17,580
  • 5
  • 58
  • 84
  • Thanks for the reply! The only detail that I'd like to avoid is the instant evaluation of `getDayLimit` which happens with the snippet you provided. It will need to be evaluated only when the third function in the composition is called and now `getDayLimit` is calculated before the composition is fully declared. In a real example, `getDayLimit` will be an asynchronous function doing some computations that shouldn't be run before the previous functions in the composition successfully finish. Is there a way around that? – tssr Mar 29 '21 at 16:27
  • 2
    @tssr I personally wouldn't try to shove it into a function composition in this case. It seems like getting the day limit is something you could work out separately. When you need to make a decision based on the result of multiple asynchronous processes your best bet is probably to use something like RxJS instead. Which isn't incompatible with Ramda or functional programming btw. – customcommander Mar 29 '21 at 17:27
  • Ok, I will take a look at RxJS. I'm just trying to do as much as possible in a function composition to learn it, RxJS was on my learning list just after some basic functional programming. Thanks for your help! – tssr Mar 29 '21 at 21:58