Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Point-free composition of lenses in Ramda.js

I am trying to compose functions that return lenses, to produce a new lens, and do it in a point-free style.

This is probably a more general question about function composition. Lenses are just a case-study. I am not interested in lenses specifically, but I want to know the general pattern for how to compose these functions in a point-free way.

const obj = {a: {x: 0}, b: {x: 42}};

// this won't work, but I want it to work
const pointFreeComposedLens = R.compose(R.lensProp, R.lensProp('x'));
R.view(pointFreeComposedLens('a'), obj); // returns 'undefined'

// this works
const pointyComposedLens = key => R.compose(R.lensProp(key), R.lensProp('x'));
R.view(pointyComposedLens('a'), obj); // returns '0'

What is the pattern for composing functions so that I don't need to keep re-writing the arguments for the first function in the composition pipeline?

For an egregious example:

const deepLens = (a, b, c) => R.lensPath([a, b, c]);

// This works, but is tedious & verbose
const extraDeep = (a, b, c, x) => R.compose(deepLens(a,b,c), R.lensProp(x));
const gammaDeep = (a, b, c, y) => R.compose(deepLens(a,b,c), R.lensProp(y));

// Doesn't work, but it would be nicer to write:
const extraDeep = x => R.compose(deepLens, R.lensProp(x));

// and call it like so:
R.view(extraDeep('a','b','c','x'), obj);
like image 924
the-fool Avatar asked Jan 22 '26 23:01

the-fool


1 Answers

I know you're looking at lenses only as an example, but here is one way to get something like the behavior I think you want from them.

const {lensPath, compose, lens, view} = R

const deepLens = (a, b, c) => lensPath([a, b, c]);
const deeper = (lens, ...args) => compose(lens, lensPath(args))

const cLens = deepLens('a', 'b', 'c')
const obj =  {a: {b: { c: {d: 1, e: 2, f: {g: 3, h: 4, i: {j: 5, k: 6}}}}}}

console.log(view(cLens, obj)) //=> {d: 1, e: 2, f: {g: 3, h: 4, i: {j: 5, k: 6}}}
console.log(view(deeper(cLens, 'f', 'g'), obj)) //=> 3

const fLens = deeper(cLens, 'f')

console.log(view (fLens, obj)) //=> {g: 3, h: 4, i: {j: 5, k: 6}}

const jLens = deeper(cLens, 'f', 'i', 'j')
// or jLens = deeper(fLens, 'i', 'j')

console.log(view(jLens, obj)) //=> 5
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

As to the broader composition question, lenses are generally a special case for a library like Ramda, as the composition is in the opposite order than usually expected. (The technical reasons are too much to go into here.)

But that's why this doesn't work:

const extraDeep = x => R.compose(deepLens, R.lensProp(x));

Ramda does allow the first function in a composition chain (rightmost in compose, leftmost in pipe to receive additional arguments. But when the composition order is reversed with lens composition, it doesn't do what you might like.

So if you are having similar issues with composition in another context, please open a separate question. I'd be curious to see what you're looking for.

like image 107
Scott Sauyet Avatar answered Jan 24 '26 12:01

Scott Sauyet



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!