2

I am trying to better understand functional composition in ramda.js and currently have a method in a class that looks like this:

const replace = (newItem) => function3(function1(newItem).function2(newItem));

I know in ramda.js for a simpler function like

const replace = (newItem) => function2(function1(newItem));

you could write it like

const replace = compose(function2, function1);

Is there a similar way to do the same with the initial function using functional composition / application or other ramda.js helper methods?

2
  • 1
    Duplicating the newItem argument is really much harder in point-free style. Don't try it. Commented Jul 26, 2018 at 19:34
  • @Bergi is right, if anyone wants to try this, don't. R.compose(function3, R.converge(R.call, [R.compose(R.flip(R.invoker(1,'function2')), function1), R.identity])) is not acceptable from a maintenance standpoint. It also increases the number of function calls an absurd amount. Commented Jul 27, 2018 at 15:05

2 Answers 2

2

Ramda has two functions that should help with this. The more standard one is lift. Many functional languages have this concept. One way to think about it is that it lifts a function which operates on values to create one that operates on containers of those values:

add(3, 5) //=> 8
lift(add)([3], [5]) //=> [8]

Functions can be seen as containers of values too. Functions which return values of a given type can be considered containers for that type.

So we can lift your function3 to operate not on values, but on containers for those values, and then supply it the input to those functions. Here's an example with arrays as containers:

const function1 = newItem => `function1(${newItem})`
const function2 = newItem => `function2(${newItem})`
const function3 = (v1, v2) => `function3(${v1}, ${v2})`

const combine = R.lift(function3)(function1, function2)

console.log(combine('foo')) //=> "function3(function1(foo), function2(foo))"
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>


The less standard function is converge. This is focused only on functions, and not on arbitrary containers. It works similarly in this case. The function is created in one pass rather than the two for lift. And that means the initial functions need to be wrapped in an array:

const function1 = newItem => `function1(${newItem})`
const function2 = newItem => `function2(${newItem})`
const function3 = (v1, v2) => `function3(${v1}, ${v2})`

const combine = R.converge(function3, [function1, function2])

console.log(combine('bar')) //=> "function3(function1(bar), function2(bar))"
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

converge is only for functions, but it can work with polyadic functions. lift will work only with unary ones. converge is a Ramda-specific function. I haven't seen it elsewhere. So, if lift will work for you, I would suggest you choose it instead.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks - This looks close, but the function I'm looking for chains off the result of the first. Your solution is function3(function1(bar), function2(bar)) but I'm looking for function3(function1(bar).function2(bar)) (note the dot) I was looking at converge before posting this question, but am not quite sure how to do it. I do believe it is possible using R.invoke and some variation of lift, converge, or compose, but I can't quite get it.
Ah yes, I did misread. Ramda doesn't offer much to help with methods, focusing instead on functions.
Per the other answer provided, it is in fact possible, however the complexity to comprehend it makes the non point-free style much more reasonable so I'll probably stick to it
1

So your question is how to write

function1(input).function2(input)

In a functional way. If I am correct, here is how:

First let's create a function method that would give us a method of an object bound to that object:

const method = R.curry((name, object) => R.bind(object[name], object))

Using this function, we can rewrite our expression as

method('function2', function1(input))(input)

But we want something cleaner and more re-usable, right? So let's do some refactoring

method('function2', function1(input))(input)

method('function2')(function1(input))(input)

R.pipe(function1, method('function2'))(input)(input) // or use R.compose

R.converge(R.call, [
  R.pipe(function1, method('function2')),
  R.identity
])(input)

Now we can define our function combine like so

const combine = (fn, name) =>
  R.converge(R.call, [
    R.pipe(fn, method(name)),
    R.identity
  ])

And the expression becomes

combine(function1, 'function2')(input)

I hope my explanation is clear and it solves your problem :)

1 Comment

This works, method could be replaced with ramda's invoker function which does essentially the same thing. e.g. R.converge(R.call, [R.compose(R.flip(R.invoker(1,'function2')), function1), R.identity]) so the solution to my issue is R.compose(function3, R.converge(R.call, [R.compose(R.flip(R.invoker(1,'function2')), function1), R.identity])) ~ Thanks! As a side note, it probably isn't worth using as it is fairly difficult to read / comprehend so I'll probably just use the non point free version.

Your Answer

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.