We explore what makes lenses tick, and experiment with functors along the way. (12-15 min. read)
我们探索是什么让透镜滴答作响,并在此过程中用函子进行实验(12-15分钟(阅读)
Check out Ramda’s lens source code. As of this course, it looks like this
var lens = _curry2(function lens(getter, setter) {
return function(toFunctorFn) {
return function(target) {
return map(
function(focus) {
return setter(focus, target);
},
toFunctorFn(getter(target))
);
};
};
});
There’s a lot to unpack here but I think we can do it. Starting with the first line…
Currying #
Look at the top of the source code file.
import _curry2 from './internal/_curry2';
Ok, not too scary–they imported an internal _curry2 function. Why?
好的,不太可怕-他们导入了一个内部的_curry2函数。为什么?
var lens = _curry2(function lens(getter, setter) {
It’s currying lens. Ramda curries everything so _curry2 seems to be specialized for functions with two arguments.
这是咖喱镜头。Ramda处理所有事情,因此,curry2似乎专门用于具有两个参数的函数。
Why a specialized curry()? #
为什么是专门的咖喱#
Ramda的curry是动态的,可以处理具有不同参数计数的所有类型的函数。
但是,如果您知道需要多少参数,那么使用专门的curry可以优化函数的运行时。
您可以忽略处理任何其他参数情况,因为您知道您的函数需要2、3、4等参数。 下一行!
Ramda’s curry is dynamic, and handles all types of functions with differing argument counts.
If you know how many arguments you need, however, using a specialized curry can optimize your function’s run-time. You can ignore handling any other argument case, because you know your function expects 2, 3, 4, etc arguments.
Next line!
var lens = _curry2(function lens(getter, setter) {
return function(toFunctorFn) {
So lens returns a function after receiving its getter and setter. Let’s test that out.
因此,lens在接收其getter和setter后返回一个函数。让我们测试一下。
import { assoc, lens, prop } from 'ramda';
const name = lens(prop('name'), assoc('name'));
console.log(name.toString());
function (toFunctorFn) {
return function (target) {
return map(function (focus) {
return setter(focus, target);
}, toFunctorFn(getter(target)));
};
}
Yep! This returned a function that takes one parameter, toFunctorFn. “To Functor Function”
Sounds like a function that turns something into a functor. We know from the previous section that functors are containers that can hold any value.
是的!这返回了一个接受一个参数toFunctorFn的函数。”“到函子函数” 听起来像是一个函数,它能把某些东西变成函子。
从上一节我们知道函子是可以保存任何值的容器。
Using Functors #
What’s the next step after giving lens a getter/setter? Pass it to view, set, or over.
使用函子# 给lens一个getter/setter后下一步是什么?将其传递给查看、设置或覆盖。
import { assoc, lens, prop, view } from 'ramda';
const name = lens(prop('name'), assoc('name'));
const result = view(name, { name: 'Bobo' });
console.log({ result });
//{ result: 'Bobo' }
That’s pretty cool. With the getter/setter a lens looks like this
那很酷。使用getter/setter时,镜头如下所示
function (toFunctorFn) {
return function (target) {
return map(function (focus) {
return setter(focus, target);
}, toFunctorFn(getter(target)));
};
}
After giving it to view, though…
view(name, { name: 'Bobo' });
…everything’s magically resolved and we get ‘Bobo’.
…一切都神奇地解决了,我们得到了“波波”。
View Magic #
That means view satisfied all of lens’ requirements in a single shot, so it’s our next point of investigation. Take a look at its source code. There’s a variable, Const.
观赏魔术# 这意味着在一次拍摄中,视角满足了镜头的所有要求,所以这是我们的下一个调查点
。看看它的源代码。有一个变量,Const。
var Const = function(x) {
return {
value: x,
'fantasy-land/map': function() { return this; }
};
};
console.log(Const('Hello World'));
//{ value: 'Hello World',
'fantasy-land/map': [Function: fantasyLandMap] }
It creates a functor with two properties
- value
- fantasy-land/map method
We’ve already seen this. It wants to override map’s functionality.
Now look at view’s implementation.
它创建了一个具有两个属性的函子
价值
幻想土地/地图法
我们已经看到了这一点。
它想要覆盖地图的功能。 现在看看view的实现。
var view = _curry2(function view(lens, x) {
return lens(Const)(x).value;
});
Const is the toFunctorFn here. It will tell lens how to turn something into a functor.
Let’s extract that step into our own playground to experiment a little. We just need to define our own Const and give it to the name lens we created earlier.
Const是这里的toFunctorFn。它将告诉lens如何将某个东西变成函子。 让我们把这一步提取到我们自己的操场上做一点实验。我们只需要定义我们自己的常量,并将其命名为我们之前创建的名称lens。
import { assoc, lens, prop, view } from 'ramda';
// Define Const
var Const = function(x) {
return {
value: x,
'fantasy-land/map': function() { return this; }
};
};
const name = lens(prop('name'), assoc('name'));
// And use it here
const withFunctorFn = name(Const);
console.log(withFunctorFn.toString());
function (target) {
return map(function (focus) {
return setter(focus, target);
}, toFunctorFn(getter(target)));
}
Yet Another Function #
It returned another function, when will the madness end?!
function (target) {
return map(function (focus) {
return setter(focus, target);
}, toFunctorFn(getter(target)));
}
This one needs a target. Let’s see how view satisfies this requirement.
这个需要一个目标。让我们看看view如何满足这个要求。
var view = _curry2(function view(lens, x) {
return lens(Const)(x).value;
});
view’s second argument, x
视图的第二个参数x
function view(lens, x)
gets forwarded to lens
被转发到镜头
lens(Const)(x)
import { assoc, lens, prop, view } from 'ramda';
// Define Const
var Const = function(x) {
return {
value: x,
'fantasy-land/map': function() { return this; }
};
};
const name = lens(prop('name'), assoc('name'));
// Use it here
const withFunctorFn = name(Const);
// Then pass a target
const withTarget = withFunctorFn({ name: 'Bobo' });
console.log(withTarget);
{ value: 'Bobo',
'fantasy-land/map': [Function: fantasyLandMap] }
Whoa whoa, look at that!
That’s our desired value, Bobo, after passing through Const. It’s now a functor!
这是我们期望的值,Bobo,在通过Const之后。它现在是一个函子!
console.log(Const('Bobo'));
//{ value: 'Bobo',
'fantasy-land/map': [Function: fantasyLandMap] }
So we somehow get back a functor(Bobo) after this code runs
因此,在代码运行之后,我们不知何故得到了一个函子(Bobo)
return function(target) {
return map(
function(focus) {
return setter(focus, target);
},
toFunctorFn(getter(target))
);
};
After getting its target, lens begins mapping.
获得目标后,镜头开始映射。
Why map()? #
But mapping is to change a functor’s value. view’s only supposed to get a value, not modify it!
map does change values, but not if you override it…
Let’s dissect the following code snippet.
为什么映射()#
但映射就是改变函子的值。
视图只应该获取一个值,而不是修改它!
贴图确实会更改值,但如果覆盖它,则不会更改值…
让我们分析一下下面的代码片段。
return map(
function(focus) {
return setter(focus, target);
},
toFunctorFn(getter(target))
);
What are the pieces? 这些是什么?
getter = prop('name');
target = { name: 'Bobo' };
toFunctorFn = function(x) {
return {
value: x,
'fantasy-land/map': function() { return this; }
};
};
look closer at that functor’s fantasy-land/map method.
`fantasy-land/map`: function() { return this; }
Overridden to Do Nothing #
It does nothing but return itself.
This makes sense because view’s job is to return the data untouched. By returning this it effectively ignores map.
To contrast, look at over’s toFunctorFnin the source code.
无事可做#
它什么也不做,只是自己返回。
这是有意义的,因为视图的任务是返回未触及的数据。
通过返回该值,它实际上忽略了映射。
相比之下,请查看源代码中over的toFunctorFn。
var Identity = function(x) {
return {value: x, map: function(f) { return Identity(f(x)); }};
};
It’s an Identity functor, similar to the previous lesson!
Instead of doing nothing like view, map’s being told to transform the lens value according to the supplied function.
这是一个身份函子,类似于上一课! 与“视图”不同的是,“贴图”被告知根据提供的函数变换镜头值。
import { assoc, lens, over, prop, toUpper } from 'ramda';
const name = lens(prop('name'), assoc('name'));
const result = over(name, toUpper, { name: 'Bobo' });
console.log({ result });
//{ result: { name: 'BOBO' } }
So whether you’re reading with view or writing with set/over, your toFunctorFn is the key to making it all work with map.
因此,无论您是使用view阅读还是使用set/over写作,
您的toFunctorFn都是使用map实现所有功能的关键。
Summary #
- Lenses use functors, therefore implement everything, even reading a value, with map.
- This is possible because an object can define its own fantasy-land/map method to take full control of the procedure.
- In view’s case, fantasy-land/map just returns itself (the requested data).
- In over’s case, fantasy-land/map transforms the data with a given function.
We’re exploring how lenses compose next. That’s right, we’re going to start combining them to find shocking results!
总结#
镜头使用函子,因此实现一切,甚至读取一个值,与地图。
这是可能的,因为对象可以定义自己的幻想土地/地图方法来完全控制该过程。
在view中,fantasy land/map只返回它自己(请求的数据)。
在over的例子中,fantasy land/map使用给定函数转换数据。
下一步我们将探索镜头的构图方式。
没错,我们将开始结合它们来发现令人震惊的结果!