[英]Isn't it redundant for Control.Lens.Setter to wrap types in functors?
I'm watching the Control.Lens
introduction video . 我正在观看Control.Lens
介绍视频 。
It makes me wonder why is it needed for the Setter
type to wrap things in functors. 这让我想知道为什么Setter
类型需要在函子中包装东西。
It's (roughly) defined like this: 它(大致)定义如下:
type Control.Lens.Setter s t a b = (Functor f) => (a -> f a) -> s -> f t
Let's say I have a data called Point
that's defined like this: 假设我有一个名为Point
的数据,其定义如下:
data Point = Point { _x :: Int, _y :: Int } deriving Show
Then I can write my own xlens
like this: 然后我可以像这样编写自己的xlens
:
type MySetter s t a b = (a -> b) -> s -> t
xlens :: MySetter Point Point Int Int
xlens f p = p { _x = f (_x p) }
And I can use it like this: 我可以像这样使用它:
p = Point 100 200
xlens (+1) p -- Results in Point { _x = 101, _y = 200 }
By using Control.Lens
, the same effect is achieved by: 通过使用Control.Lens
,可以通过以下方式实现相同的效果:
over x (+1) p
where the following stands: 以下是:
x :: Functor f => (Int -> f Int) -> Point -> f Point
over :: Setter Point Point Int Int -> (Int -> Int) -> Point -> Point
So my question is, since the same effect can be achieved in a simpler manner, why Control.Lens
wraps things in functors? 所以我的问题是,既然可以用更简单的方式实现相同的效果,为什么Control.Lens
在函子中包装? It looks redundant to me, since my xlens
does the same as Control.Lens
's over x
. 它看起来多余,因为我的xlens
与Control.Lens
的over x
。
Just for the record, I can also chain my lenses in the same manner: 只是为了记录,我也可以用同样的方式链接我的镜头:
data Atom = Atom { _element :: String, _pos :: Point } deriving Show
poslens :: MySetter Atom Atom Point Point
poslens f a = a { _pos = f (_pos a) }
a = Atom "Oxygen" p
(poslens . xlens) :: (Int -> Int) -> Atom -> Atom
(poslens . xlens) (+1) a -- Results in Atom "Oxygen" (Point 101 200)
This is a wonderful question and will require a little bit of unpacking. 这是一个很好的问题,需要一点点拆包。
I want to gently correct you on one point right off the bat: the type of Setter in the lens
package as of recent versions is 我想在蝙蝠的一点上轻轻地纠正你:截至最近版本的lens
包装中的Setter类型是
type Setter s t a b = (a -> Identity b) -> s -> Identity t
No Functor
in sight ... yet. 看不到Functor
......
That does not invalidate your question however. 但这并不会使您的问题无效。 Why isn't the type simply 为什么不是简单的类型
type Setter s t a b = (a -> b) -> s -> t
For that we first have to talk about Lens
. 为此我们首先要谈论Lens
。
A Lens
is a type that allows us to perform both a getter and a setter operation. Lens
是一种允许我们执行getter和setter操作的类型。 These two combined form one beautiful functional reference. 这两个组合形成了一个美丽的功能参考。
A simple pick for Lens
type is: Lens
类型的简单选择是:
type Getter s a = s -> a
type Setter s t a b = (a -> b) -> s -> t
type Lens s t a b = (Getter s a, Setter s t a b)
This type however is deeply unsatisfying. 然而,这种类型非常不满意。
.
它没有成功.
, which is perhaps the single best selling point of the lens
package. ,这可能是lens
包装的唯一最佳卖点。 view
) and setters (like over
) cannot take lenses because their types are so different. 最重要的一点是:吸气剂(如view
)和定位器(如over
)的功能无法拍摄镜头,因为它们的类型非常不同。 Without that last problem solved why even bother writing a library? 如果没有最后一个问题解决了为什么甚至打扰写一个库? We would hate for users to have to constantly think about where they are in the UML hierarchy of optics, adjusting their function calls each time they move up or down. 我们讨厌用户不得不经常考虑它们在UML光学层次结构中的位置,每次上下移动时调整它们的函数调用。
The question of the moment is then: is there a type we can write down for Lens
such that it is automatically both a Getter
and a Setter
? 那时候的问题是:我们可以为Lens
写下一个类型,它自动是Getter
和Setter
吗? And for that we have to transform the types of Getter
and Setter
. 为此,我们必须改变Getter
和Setter
的类型。
First note that s -> a
is equivalent to forall r. (a -> r) -> s -> r
首先要注意的是s -> a
相当于forall r. (a -> r) -> s -> r
forall r. (a -> r) -> s -> r
. forall r. (a -> r) -> s -> r
。 This transformation into continuation passing style is far from obvious. 这种转变为延续传递方式远非显而易见。 You might be able to intuit this transformation as this: "A function of type s -> a
is a promise that given any s
you can hand me an a
. But that should be equivalent to the promise that given a function that maps a
to r
you can hand me a function that maps s
to r
also." 您可能能够直觉这个转变是这样的:“类型的函数s -> a
是给定一个承诺任何s
你可以递给我的a
,而应该是相当于给映射功能的承诺。 a
到r
你可以递给我一个函数映射s
到r
也“。 Maybe? 也许? Maybe not. 也许不吧。 There might be a leap of faith involved here. 这里可能有一个信仰的飞跃。
Now define newtype Const ra = Const r deriving Functor
. 现在定义newtype Const ra = Const r deriving Functor
。 Note that Const ra
is the same as r
, mathematically and at runtime. 请注意, Const ra
在数学上和运行时与r
相同。
Now note that type Getter sa = forall r. (a -> r) -> s -> r
现在请注意type Getter sa = forall r. (a -> r) -> s -> r
type Getter sa = forall r. (a -> r) -> s -> r
can be rewritten as type Getter stab = forall r. (a -> Const rb) -> s -> Const rt
type Getter sa = forall r. (a -> r) -> s -> r
可以改写为type Getter stab = forall r. (a -> Const rb) -> s -> Const rt
type Getter stab = forall r. (a -> Const rb) -> s -> Const rt
. type Getter stab = forall r. (a -> Const rb) -> s -> Const rt
。 Though we introduced new type variables and mental anguish for ourselves this type is still mathematically identical to what we started out with ( s -> a
). 虽然我们为自己引入了新类型变量和精神痛苦,但这种类型在数学上仍然与我们开始时的相似( s -> a
)。
Define newtype Identity a = Identity a
. 定义新类型newtype Identity a = Identity a
。 Note that Identity a
is the same as a
, mathematically and at runtime. 请注意, Identity a
在数学上和运行时与a
相同。
Now note that type Setter stab = (a -> Identity b) -> s -> Identity t
is still identical to the type that we started out with. 现在请注意, type Setter stab = (a -> Identity b) -> s -> Identity t
仍然与我们开始使用的类型相同。
With this paperwork out of the way, can we unify setters and getters into one single Lens
type? 通过这个文书工作,我们可以将setter和getter统一到一个单一的Lens
类型中吗?
type Setter s t a b = (a -> Identity b) -> s -> Identity t
type Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
Well this is Haskell and we can abstract out the choice of Identity
or Const
to a quantified variable. 那么这就是Haskell,我们可以将Identity
或Const
的选择抽象为量化变量。 As the lens wiki says , all that Const
and Identity
have in common is that each is a Functor
. 正如镜头维基所说 , Const
和Identity
的共同之处在于每个都是Functor
。 We then choose that as a sort of point of unification for these types: 然后我们选择它作为这些类型的统一点:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
(There are other reasons to choose Functor
too, such as to prove the laws of functional references by using free theorems. But we will handwave a little bit here for time.) That forall f
is like the forall r
. (还有其他理由选择Functor
,例如通过使用自由定理来证明函数引用的定律。但是我们会在这里稍微手动一点。) forall f
就像forall r
。 above – it lets consumers of the type choose how to fill the variable in. Fill in an Identity
and you get a setter. 上面 - 它允许该类型的消费者选择如何填充变量。填写一个Identity
,你得到一个setter。 Fill in a Const a
and you get a getter. 填写一个Const a
,你得到一个吸气剂。 It was by choosing small and careful transformations along the way that we were able to arrive at this point. 通过选择我们能够在此时到达的方式进行小而细致的转换。
It might be important to note that this derivation is not the original motivation for the lens
package. 值得注意的是,这种推导并不是 lens
包装的最初动机。 As the Derivation wiki page states explains, you can start from the interesting behavior of (.)
with certain functions and tease out optics from there. 正如衍生维基页面状态所解释的那样,您可以从(.)
的有趣行为开始,使用某些功能并从那里取出光学元件。 But I think this path we carved out is a little better at explaining the question you posed, which was a big question I had starting out too. 但是我认为我们制定的这条道路在解释你提出的问题方面要好一些,这也是我开始提出的一个大问题。 I also want to refer you to lens over tea , which provides yet another derivation. 我也想把你推荐给镜头茶 ,这提供了另一种推导。
I think these multiple derivations are a good thing and a kind of dipstick for the healthiness of the lens
design. 我认为这些多种推导是一件好事,也是一种lens
设计健康性的量油尺。 That we are able to arrive at the same elegant solution from different angles means that this abstraction is robust and well-supported by different intuitions and mathematics. 我们能够从不同角度得出同样优雅的解决方案,这意味着这种抽象是强大的,并得到不同直觉和数学的良好支持。
I also lied a little bit about the type of Setter in recent lens
. 我还对最近lens
的Setter类型撒了一点谎言。 It's actually 实际上
type Setter s t a b = forall f. Settable f => (a -> f b) -> s -> t b
This is another example of abstracting the higher-order type in optical types to provide the library user a better experience. 这是抽象光学类型中的高阶类型的另一个例子,以便为图书馆用户提供更好的体验。 Almost always f
will be instantiated to Identity
, as there is an instance Settable Identity
. 几乎总是f
将实例化为Identity
,因为有一个instance Settable Identity
。 However every now and then you might want to pass setters to the backwards
function, which fixes f
to be Backwards Identity
. 但是,您可能偶尔会将setter传递给backwards
函数,这会将f
修复为Backwards Identity
。 We can probably categorize this paragraph as "more information about lens
than you probably wanted to know." 我们可以将这一段分类为“关于lens
更多信息,而不是你可能想知道的。”
In a sense, the reason lens
wraps setters in functor-returns is that they would be too powerful otherwise. 从某种意义上说, lens
在算子返回中包装setter的原因是它们会过于强大 。
In fact, when a setter is used , the functor will be instantiated to Identity
anyway, which is exactly like your proposed signature. 事实上,当使用一个二传手,仿函数将被实例Identity
无论如何,这正是喜欢你提出的签名。 However, the implementation of a setter must not exploit this fact . 但是, setter的实现不得利用这一事实 。 With your signature, I could write something like 有了你的签名,我可以写出类似的东西
zlens :: MySetter Point Point Int Int
zlens _f p = p -- no z here!
Well, this is just not possible with the Functor
based signature, because zlens
would need to be universally quantified over the functor it could not know how to inject a result into the f
wrapper. 好吧,基于Functor
的签名是不可能的,因为zlens
需要通过functor进行普遍量化,它不知道如何将结果注入f
包装器。 The only way to get a result of the functor type is to first apply the setter-function to a field of the right type! 获得仿函数类型结果的唯一方法是首先将setter函数应用于正确类型的字段!
So, this is just a nice free theorem † . 所以,这只是一个很好的自由定理† 。
More practically, we need the functor wrapper for compatibility . 更实际的是,我们需要functor包装器以实现兼容性 。 While you can define setters without this wrapper, this is not possible for getters, as these use Const
rather than Identity
, and need the added polymorphism in the first argument of this type constructor. 虽然您可以在没有此包装器的情况下定义setter ,但这对于getter来说是不可能的,因为它们使用Const
而不是Identity
,并且需要在此类型构造函数的第一个参数中添加多态性。 By requiring such a wrapper for all lens flavours (only with different class constraints), we can use the same combinators for all of them, yet the type system will always collapse the functionality down to whichever features are actually applicable to the situation. 通过要求所有镜片口味的包装(仅具有不同的类别限制),我们可以为所有镜片使用相同的组合器,但类型系统将始终将功能折叠到实际适用于该情况的任何功能。
† Thinking about it, the guarantee is actually not very strong... I can still subvert it with some fmap (const old)
cheatery, but it's certainly not something that could realistically happen by mistake. † fmap (const old)
,保证实际上并不是很强大......我仍然可以用一些fmap (const old)
cheatery颠覆它,但它肯定不会真实地发生错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.