To cap things off, let’s talk about how lenses compose. Spoiler alert: it looks backwards, but it’s not. (8 min. read)
最后,让我们来谈谈镜头是如何构成的。剧透警报:它看起来向后看,其实但不是(8分钟(阅读)

Let’s go back a few lessons and review our code from Use With Arrays. We wished to access an object’s third friend using lensIndex.
让我们回顾几节课,回顾一下与数组一起使用的代码。我们希望使用lensIndex访问对象的第三个朋友。

  1. import { lensIndex, view } from 'ramda';
  2. const person = {
  3. firstName: 'Bobo',
  4. lastName: 'Flakes',
  5. friends: [{
  6. firstName: 'Clark',
  7. lastName: 'Kent'
  8. }, {
  9. firstName: 'Bruce',
  10. lastName: 'Wayne'
  11. }, {
  12. firstName: 'Barry',
  13. lastName: 'Allen'
  14. }]
  15. };
  16. const getThirdFriend = lensIndex(2);
  17. const result = view(getThirdFriend, person.friends);
  18. console.log({ result });
  1. { result: { firstName: 'Barry', lastName: 'Allen' } }

A Bit Too Specific #

It works fine, but look at how view’s being used.
有点太具体了# 它工作得很好,但是看看视图是如何使用的。

  1. view(getThirdFriend, person.friends);
  2. // person.friends?

Lenses help decouple your logic and data, so it’s counterintuitive to specify person.friends. The whole point’s to just pass in person and let the lens do the work for us!
But lensIndex only works on arrays, so how can it focus on friends before the index? Say it with me: function composition! We’ll just compose lensIndex with lensProp and get our result.

镜头有助于分离逻辑和数据,
因此指定person.friends是违反直觉的。
关键是要亲自过去,让镜头为我们工作!
但是lensIndex只在数组上工作,所以它怎么能在索引之前关注朋友呢?
跟我说:函数合成!
我们将用lensProp合成lensIndex并得到结果。

  1. import { pipe, lensIndex, lensProp, view } from 'ramda';
  2. const person = {
  3. firstName: 'Bobo',
  4. lastName: 'Flakes',
  5. friends: [{
  6. firstName: 'Clark',
  7. lastName: 'Kent'
  8. }, {
  9. firstName: 'Bruce',
  10. lastName: 'Wayne'
  11. }, {
  12. firstName: 'Barry',
  13. lastName: 'Allen'
  14. }]
  15. };
  16. const getThirdFriend = pipe(
  17. lensProp('friends'),
  18. lensIndex(2)
  19. );
  20. const result = view(getThirdFriend, person);
  21. console.log({ result });
  22. //{ result: undefined }

This Is Wrong #

There ya go, nice and…wait. This returns undefined… Why?! The composition looks correct.

  1. const getThirdFriend = pipe(
  2. lensProp('friends'),
  3. lensIndex(2)
  4. );
  1. Get friends
  2. Get third one (index 2)

    Believe it or not, we composed them backwards. Check this out.
    信不信由你,我们是倒着写的。看看这个。
    ```javascript import { pipe, lensIndex, lensProp, view } from ‘ramda’;

const person = { firstName: ‘Bobo’, lastName: ‘Flakes’, friends: [{ firstName: ‘Clark’, lastName: ‘Kent’ }, { firstName: ‘Bruce’, lastName: ‘Wayne’ }, { firstName: ‘Barry’, lastName: ‘Allen’ }] };

// Flip the composition const getThirdFriend = pipe( lensIndex(2), lensProp(‘friends’), );

const result = view(getThirdFriend, person);

console.log({ result });

  1. ```javascript
  2. { result: { firstName: 'Barry', lastName: 'Allen' } }

This Is Correct #

  1. const getThirdFriend = pipe(
  2. lensIndex(2),
  3. lensProp('friends'),
  4. );

Now it works. Why?

Back to the Functor #

The last lesson introduced the relationship between functors and lenses.
After receiving a getter/setter, the lens requires a toFunctorFn–a function that turns a value into a functor.
This is because Ramda’s map relinquishes control to any functor carrying that special fantasy-land/map property, allowing view, set, and over to do their jobs.
Look again at our composition.
现在它起作用了。为什么?
回到函子#
上一课介绍了函子和透镜之间的关系。
在收到getter/setter后,镜头需要一个toFunctorFn–一个将值转换为函子的函数。
这是因为Ramda的地图将控制权让给任何携带特殊幻想土地/地图属性的函子,允许查看、设置和覆盖它们的工作。
再看看我们的合成。

  1. const getThirdFriend = pipe(
  2. lensIndex(2),
  3. lensProp('friends'),
  4. );

So lensIndex(2) returns a function expecting its toFunctorFn, as does lensProp(‘friends’).
Following the pipe sequence leads us to an interesting conclusion: lensIndex(2) is the toFunctorFn to lensProp(‘friends’)! This is how they’re composing. We can prove it with some logs.
因此,lensIndex(2)返回一个期望其toFunctorFn的函数,lensProp(’friends’)也是如此。
按照管道顺序,我们得出了一个有趣的结论:
lensIndex(2)是从toFunctorFn到lensProp(“朋友”)的关系!
这就是他们的作曲方式。我们可以用一些日志来证明这一点。

  1. import { tap, pipe, lensIndex, lensProp, view } from 'ramda';
  2. const person = {
  3. firstName: 'Bobo',
  4. lastName: 'Flakes',
  5. friends: [{
  6. firstName: 'Clark',
  7. lastName: 'Kent'
  8. }, {
  9. firstName: 'Bruce',
  10. lastName: 'Wayne'
  11. }, {
  12. firstName: 'Barry',
  13. lastName: 'Allen'
  14. }]
  15. };
  16. // Flip the composition
  17. const getThirdFriend = pipe(
  18. tap((fn) => {
  19. console.log('lensIndex will be called with this\n');
  20. console.log(fn.toString());
  21. console.log('\n');
  22. }),
  23. lensIndex(2),
  24. tap((fn) => {
  25. console.log('lensProp will be called with this\n');
  26. console.log(fn.toString());
  27. console.log('\n');
  28. }),
  29. lensProp('friends'),
  30. tap((fn) => {
  31. console.log('The composition is this:\n');
  32. console.log(fn.toString());
  33. console.log('\n');
  34. }),
  35. );
  36. const result = view(getThirdFriend, person);
  37. console.log({ result });
  1. lensIndex will be called with this
  2. function (x) {
  3. return {
  4. value: x,
  5. 'fantasy-land/map': function () {
  6. return this;
  7. }
  8. };
  9. }
  10. lensProp will be called with this
  11. function (target) {
  12. return map(function (focus) {
  13. return setter(focus, target);
  14. }, toFunctorFn(getter(target)));
  15. }
  16. The composition is this:
  17. function (target) {
  18. return map(function (focus) {
  19. return setter(focus, target);
  20. }, toFunctorFn(getter(target)));
  21. }
  22. { result: { firstName: 'Barry', lastName: 'Allen' } }

展开# 仔细阅读这些日志。
将视图传递给lensIndex(2)和lensProp(“朋友”)的组合,创建了以下事件序列:

视图为lensIndex(2)提供了一个toFunctorFn。
现在lensIndex(2)正在等待它的数据。
lensIndex(2)成为lensProp(“friends”)的toFunctorFn
lensProp(“friends”)现在正在等待它的数据。
视图为它提供我们的个人数据。
根据getter和setter,map像火箭一样发射,以正确的顺序快速展开函子。
首先调用lensProp(“friends”)的getter,因此将检索person.friends
然后,该数据被传递给lensIndex(2),lensIndex获取第三个元素person.friends[2]。
你可以找回你的数据。

Unfold #

Carefully read these logs. Passing view to the composition of lensIndex(2) and lensProp(‘friends’) created the following sequence of events:

  1. view gave a toFunctorFn to lensIndex(2).
  2. Now lensIndex(2) awaits its data.
  3. lensIndex(2) becomes a toFunctorFn for lensProp(‘friends’)
  4. lensProp(‘friends’) now awaits its data.
  1. // the composed lens
  2. function (target) {
  3. return map(function (focus) {
  4. return setter(focus, target);
  5. }, toFunctorFn(getter(target)));
  6. }
  1. view feeds it our person data.
  2. map fires like a rocket, rapidly unfolding the functor in the correct order, according to the getters and setters.
  3. The getter for lensProp(‘friends’) is called first, so person.friends is retrieved
  4. That data is then passed to lensIndex(2), who grabs the third element, person.friends[2].
  5. You get your data back.

compose() Instead of pipe()

This takes some time to get used to. If you, like me, prefer pipe because it reads left-to-right, compose lets you write lenses left-to-right as well.
这需要一些时间来适应。如果你像我一样,喜欢看从左到右的管子,那么compose也可以让你从左到右书写镜头。

  1. const getThirdFriend = compose(
  2. lensProp('friends'),
  3. lensIndex(2)
  4. );
  1. import { compose, lensIndex, lensProp, view } from 'ramda';
  2. const person = {
  3. firstName: 'Bobo',
  4. lastName: 'Flakes',
  5. friends: [{
  6. firstName: 'Clark',
  7. lastName: 'Kent'
  8. }, {
  9. firstName: 'Bruce',
  10. lastName: 'Wayne'
  11. }, {
  12. firstName: 'Barry',
  13. lastName: 'Allen'
  14. }]
  15. };
  16. const getThirdFriend = compose(
  17. lensProp('friends'),
  18. lensIndex(2)
  19. );
  20. const result = view(getThirdFriend, person);
  21. console.log({ result });
  22. //{ result: { firstName: 'Barry', lastName: 'Allen' } }

Summary #

  • Compose lenses to handle objects and arrays at the same time (lensProp + lensIndex).
  • Lenses don’t compose backwards, one combines with the next by acting as its toFunctorFn.
  • Once built up and given the data, map takes that “giant” lens and calls its toFunctorFn, which is a composition of every lens that came before it.
  • This unfolding lets map drill all the way down through your data and return the property you’re after.
  • Use compose if you want to read lenses from left-to-right.

    总结#
    组合透镜以同时处理对象和阵列(LensPro+lensIndex)。
    镜头不会向后组合,一个镜头通过充当它的toFunctorFn与另一个镜头组合。
    一旦建立并提供了数据,地图就把那个“巨大”的镜头称为toFunctorFn,它是之前所有镜头的组合。
    通过这种展开,地图可以一直向下钻取数据,并返回所需的属性。
    如果要从左到右读取镜头,请使用“合成”。