简体   繁体   English

对于Control.Lens.Setter来说,在函子中包装类型不是多余的吗?

[英]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 . 它看起来多余,因为我的xlensControl.Lensover 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

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. 然而,这种类型非常不满意。

  • It fails to compose with . 它没有成功. , which is perhaps the single best selling point of the lens package. ,这可能是lens包装的唯一最佳卖点。
  • It is rather memory inefficient to build lots of tuples, only to rip them apart later. 构建大量元组是相当低效的内存,只是为了以后将它们分开。
  • The big one: functions that take getters (like 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写下一个类型,它自动GetterSetter吗? And for that we have to transform the types of Getter and Setter . 为此,我们必须改变GetterSetter的类型。

Getter 消气

  • 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 ,而应该是相当于给映射功能的承诺。 ar你可以递给我一个函数映射sr也“。 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 )。

Setter 二传手

  • 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仍然与我们开始使用的类型相同。

All together 全部一起

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,我们可以将IdentityConst的选择抽象为量化变量。 As the lens wiki says , all that Const and Identity have in common is that each is a Functor . 正如镜头维基所说ConstIdentity的共同之处在于每个都是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. 通过选择我们能够在此时到达的方式进行小而细致的转换。

Caveats 注意事项

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM