我正在观看Control.Lens
简介video。
这让我想知道为什么Setter
类型需要将东西包装在函子中。
它的定义大致如下:
type Control.Lens.Setter s t a b = (Functor f) => (a -> f a) -> s -> f t
假设我有一个称为
Point
的数据,其定义如下:data Point = Point { _x :: Int, _y :: Int } deriving Show
然后,我可以这样编写自己的
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) }
我可以这样使用它:
p = Point 100 200
xlens (+1) p -- Results in Point { _x = 101, _y = 200 }
通过使用
Control.Lens
,可以达到以下效果:over x (+1) p
如下所示:
x :: Functor f => (Int -> f Int) -> Point -> f Point
over :: Setter Point Point Int Int -> (Int -> Int) -> Point -> Point
所以我的问题是,由于可以以更简单的方式实现相同的效果,为什么
Control.Lens
将东西包装在函子中?这对我来说似乎是多余的,因为我的xlens
与Control.Lens
的over x
相同。出于记录目的,我也可以用相同的方式将我的镜头链接起来:
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)
最佳答案
这是一个奇妙的问题,需要一点点包装。
我想马上就一点纠正您:最新版本的lens
包中的Setter类型是
type Setter s t a b = (a -> Identity b) -> s -> Identity t
尚无
Functor
...。但这不会使您的问题无效。为什么类型不简单
type Setter s t a b = (a -> b) -> s -> t
为此,我们首先要讨论
Lens
。镜片
Lens
是一种类型,它允许我们执行getter和setter操作。两者结合形成了一个优美的功能引用。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)
但是,这种类型非常令人不满意。
.
组合,这可能是lens
软件包的最畅销点。 view
)和setter(例如over
)的函数不能使用镜头,因为它们的类型非常不同。 没有最后一个问题,解决了为什么还要费心编写一个库?我们不希望用户不得不不断考虑他们在光学UML层次结构中的位置,每次上移或下移时都要调整其函数调用。
那么现在的问题是:是否可以为
Lens
写下一种类型,使其自动既是Getter
又是Setter
?为此,我们必须转换Getter
和Setter
的类型。setter/getter
s -> a
与forall r. (a -> r) -> s -> r
等效。这种向延续传递风格的转变远非显而易见。您可能可以按如下方式进行这种转换:“s -> a
类型的函数是一个给定任何s
的 promise ,您都可以将我的a
交给我。但这应等于给定一个给定一个将a
映射到r
的函数的 promise 。给我一个也将s
映射到r
的函数。”也许?也许不吧。这里可能会有信仰的飞跃。 newtype Const r a = Const r deriving Functor
。请注意,Const r a
在数学上和运行时与r
相同。 type Getter s a = forall r. (a -> r) -> s -> r
可以重写为type Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
。尽管我们为自己引入了新的类型变量和精神痛苦,但该类型在数学上仍与我们最初使用的(s -> a
)相同。 塞特犬
newtype Identity a = Identity a
。请注意,Identity a
在数学上和运行时与a
相同。 type Setter s t a b = (a -> Identity b) -> s -> Identity t
仍然与我们最初使用的类型相同。 全部一起
有了这份文件,我们可以将 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
好吧,这是Haskell,我们可以将
Identity
或Const
的选择抽象为量化变量。与the lens wiki says一样,Const
和Identity
的共同点是它们都是Functor
。然后,我们选择这些作为这些类型的统一点:type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
(也有其他选择
Functor
的原因,例如通过使用自由定理证明函数引用的定律。但是我们会在这里稍作动摇以待时间。)forall f
类似于forall r
。上面的代码–它使类型的使用者可以选择如何填充变量。填写Identity
后,您将看到 setter 。填写Const a
,即可获得 setter/getter 。正是通过选择小而仔细的转换,我们才能够达到这一点。注意事项
可能需要注意的是,这种派生不是
lens
包的最初动机。正如Derivation wiki page states解释的那样,您可以从(.)
具有某些功能的有趣行为入手,然后从中挑逗光学元件。但是我认为,我们提出的这条路径在解释您提出的问题上要好一些,这也是我刚提出的一个大问题。我还想向您推荐lens over tea,它提供了另一种派生方式。我认为这些多重派生是一件好事,并且是
lens
设计健康的一种量油尺。我们能够从不同的 Angular 获得相同的优雅解决方案,这意味着这种抽象是强大的,并得到不同直觉和数学的良好支持。我还对最近
lens
中的Setter类型撒了些谎。其实是type Setter s t a b = forall f. Settable f => (a -> f b) -> s -> t b
这是将光学类型中的高阶类型抽象化以为库用户提供更好体验的另一个示例。因为存在
f
,所以几乎总是将Identity
实例化为instance Settable Identity
。但是,您可能不时需要将setter传递给backwards
函数,该函数将f
固定为Backwards Identity
。我们可能会将本段归类为“有关lens
的信息比您可能想知道的更多”。关于haskell - Control.Lens.Setter将类型包装在仿函数中是否不是多余的?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/37666781/