Ramda.js 中镜头的无点构图
Point-free composition of lenses in Ramda.js
我正在尝试编写 return 镜头的功能,以生产新镜头,并以无点方式进行。
这可能是一个关于函数组合的更普遍的问题。镜头只是一个案例研究。我对镜头不感兴趣,但我想知道如何以无点方式组合这些功能的一般模式。
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'
组合函数的模式是什么,这样我就不需要为组合管道中的第一个函数重新编写参数?
举个极端的例子:
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);
剩余参数将代码缩短为:
const extraDeep = (...rest) => last => R.compose(deepLens(...rest), R.lensProp(last))(rest.pop());
但我不确定这是否真的很优雅。
如果您打算编写一个接受路径和对象的函数,
那么 path
已经存在:
R.path(['a', 'b'], {a: {b: 10}}); //=> 10
如果您有兴趣删除某些函数中的某些参数,deepLens
可以重写如下:
const deepLens = R.unapply(R.lensPath);
这个无积分版本有一个额外的好处,它不仅限于三个参数。它适用于任意数量的参数:
deepLens('a', 'b'); //=> R.lensPath(['a', 'b']);
deepLens('a', 'b', 'c'); //=> R.lensPath(['a', 'b', 'c']);
deepLens('a', 'b', 'c', 'd'); //=> R.lensPath(['a', 'b', 'c', 'd']);
我知道您只是将镜片作为一个例子,但这里有一种方法可以让您获得我认为您想要的行为。
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>
至于更广泛的构图问题,镜头通常是像 Ramda 这样的库的特例,因为构图的顺序与通常预期的相反。 (技术原因太多,这里就不多说了。)
但这就是它不起作用的原因:
const extraDeep = x => R.compose(deepLens, R.lensProp(x));
Ramda 确实允许组合链中的第一个函数(compose
中最右边,pipe
中最左边的函数接收额外的参数。但是当组合顺序与镜头组合相反时,它不会不要做你想做的事。
因此,如果您在其他上下文中遇到类似的构图问题,请另开一个问题。我很想知道你在找什么。
我正在尝试编写 return 镜头的功能,以生产新镜头,并以无点方式进行。
这可能是一个关于函数组合的更普遍的问题。镜头只是一个案例研究。我对镜头不感兴趣,但我想知道如何以无点方式组合这些功能的一般模式。
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'
组合函数的模式是什么,这样我就不需要为组合管道中的第一个函数重新编写参数?
举个极端的例子:
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);
剩余参数将代码缩短为:
const extraDeep = (...rest) => last => R.compose(deepLens(...rest), R.lensProp(last))(rest.pop());
但我不确定这是否真的很优雅。
如果您打算编写一个接受路径和对象的函数,
那么 path
已经存在:
R.path(['a', 'b'], {a: {b: 10}}); //=> 10
如果您有兴趣删除某些函数中的某些参数,deepLens
可以重写如下:
const deepLens = R.unapply(R.lensPath);
这个无积分版本有一个额外的好处,它不仅限于三个参数。它适用于任意数量的参数:
deepLens('a', 'b'); //=> R.lensPath(['a', 'b']);
deepLens('a', 'b', 'c'); //=> R.lensPath(['a', 'b', 'c']);
deepLens('a', 'b', 'c', 'd'); //=> R.lensPath(['a', 'b', 'c', 'd']);
我知道您只是将镜片作为一个例子,但这里有一种方法可以让您获得我认为您想要的行为。
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>
至于更广泛的构图问题,镜头通常是像 Ramda 这样的库的特例,因为构图的顺序与通常预期的相反。 (技术原因太多,这里就不多说了。)
但这就是它不起作用的原因:
const extraDeep = x => R.compose(deepLens, R.lensProp(x));
Ramda 确实允许组合链中的第一个函数(compose
中最右边,pipe
中最左边的函数接收额外的参数。但是当组合顺序与镜头组合相反时,它不会不要做你想做的事。
因此,如果您在其他上下文中遇到类似的构图问题,请另开一个问题。我很想知道你在找什么。