用于元组扩展的镜头?

3

Control.Lens.Tuple定义了访问元组元素的镜头。例如:

class Field3 s t a b | s -> a, t -> b, s b -> t, t a -> s where
  -- | Access the 3rd field of a tuple.
  _3 :: Lens s t a b

  -- >>> (1,2,3)^._3
  -- 3
  --
  -- >>> _3 .~ "hello" $ (1,2,3)
  -- (1,2,"hello")

设置器_3 .~返回一个元组,其中第三个元素已更改,例如在此示例中更改为不同的类型。

对于类Field3,已经针对具有三个或更多元素的元组定义了实例。没有定义用于成对/双元组的实例,因为getter将得到什么呢?

但是,如果setter可以更改第n个元素的类型,为什么它不能将类型更改为不同元组度数的类型呢?

  -- >>> _3 .~ "hello" $ (1,2)
  -- (1,2,"hello")

也就是说,使用 setter _3 .~ 可以通过扩展一个二元组来设置第三个元素。
instance Field3 (a,b) (a,b,c') Dummy c' where
  _3 k ~(a,b) = k undefined <&> \c' -> (a,b,c')

Dummyundefined是一些占位符类型和值,用于不存在的二元组的第三个元素。

扩展到3元后,镜头_3像往常一样工作;同时镜头_1, _2在整个过程中都遵守着镜头法则。

问题1:这对于Setter来说有效吗?
(当然不适用于Updater/over

问题2:如果是,有没有某种方法可以使其类型错误而尝试view非存在的第三个元素?

问题3:如果Setter行不通,是否有一种extend运算符可以实现效果?(可能需要使用不同的函子。)

也许该实例可以是这样的:

instance Field3 (a,b) (a,b,c') (a,b) c' where
  _3 k ~(a,b) = k (a,b) <&> \c' -> (a,b,c')

这样view方法将会返回一个奇怪的类型结果,而over方法可以根据前两个元素来构建第三个元素。


我有点让这个工作起来了,就是 _3 .~ "hello" $ (1,2) 被接受并产生了我想要的结果。但是该实例没有被接受:与正确实例不一致,对于 FunDep t a -> s -- 这是非常正确的。我只是在类上抑制了那个 FunDep。 - AntC
2
如果你只需要一个setter (a, b) -> c -> (a, b, c),那么镜片基础设施的其余部分提供了什么是_3' = uncurry (,,)没提供的?某种通用性吗? - user11228628
我的二元组当然深度嵌套在我正在聚焦的各种其他结构中。因此,我想要一个可以作为镜头组合的东西。那么你的 _3' 就不是这样的。我看不到任何函子。 - AntC
这是一个setter。它不需要在任意的Functor上操作;它的FunctorIdentity。如果你有一个van Laarhoven镜头 forall f. Functor f => ((a, b) -> f (a, b, c)) -> s -> f t,你应该能够将其与c -> (a, b) -> Identity (a, b, c)组合,以获得你想要的更大结构的样式的setter。这就是van Laarhoven镜头的美妙之处;你可以用普通的组合做很多事情。 - user11228628
抱歉 @user11228628,我看不到。使用我的定义 set _3 "hello" (1, 2) 可以得到 (1,2,"hello"),且 _3 的类型有一个Functor的约束条件。使用你的定义 set _3' "hello" (1,2) 是无效的,而 _3' 的类型并没有提到Functor。set会引入 Identity Functor。 - AntC
1个回答

2
在van Laarhoven镜头公式中,光学元件只是一个高阶函数,它将对较小结构的操作转换为对较大结构的操作。这些函数可以组合,然后使用lens包中提供的语法糖,使调用它们变得无意识。但是它们也可以直接调用。
假设你有以下内容:
bigStructure :: (Int, (Int, (Int, Int)))
bigStructure = (0, (1, (2, 3)))

你想在最内层的一对中添加10。这是对该对的操作,它是大结构的一个较小的部分。

修改后:

你想在最深层的一对中添加数字10。这是对该对的操作,它是整个结构中的一个小部分。

mk3ple :: c -> (a, b) -> (a, b, c)
mk3ple = flip $ uncurry (,,)

因此,_2 . _2是一个聚焦于最内部一对的镜头—换句话说,它是一个将最内部一对的操作转化为大结构上的操作的函数。所以让我们把mk3ple传递给它(over会处理一些Identity繁琐的工作):


over (_2 . _2) (mk3ple 10) bigStructure

在这里,你可以使用任何需要的镜头来聚焦于元组,从而设置(概念上的,而不是字面意义上的SetterbigStructure


当然,如果你更喜欢将元组添加器作为一个具有getter和其他功能的实际镜头,你也可以这样做!只需将unit视为每个元组的一部分,并编写:

mk3ple' :: Lens (a, b) (a, b, c) () c
mk3ple' = lens (const ()) (uncurry (,,))

set (_2 . _2 . mk3ple') 10 bigStructure

“tuple-appender” 是一个实际的镜头,具有getter和其他一切功能。是的:一旦二元组扩展为三元组,则可以获取/设置第三个元素。然后 mk3ple' 不是那样的。view mk3ple' (1,2,3) 类型不正确。view mk3ple' (1,2) 类型正确,但始终返回虚拟的 ()set mk3ple' "hello" (1,2,3) 类型不正确。因此,我认为为了避免在类 Field3 上破坏 FunDep t a -> s,创建另一个类,它类似于它,但没有该FunDep,并且具有与 _3 相同的方法,用于 instance Field3x (a,b,c) (a,b,c') ...,但对于 instance Field3x (a,b) (a,b,c') ...,则根据我的 O.P.。 - AntC
我想在每次访问时使用相同命名的镜头。因为有了镜头,否则我需要仅使用一个名称来扩展,然后对于view/over使用不同的名称。这将很难阅读和脆弱的代码:如果重新排序操作,则无法轻松地进行重构。 - AntC
1
否则我需要使用一个名称仅扩展一次,然后使用不同的名称来进行view/over。嗯,它们确实是不同的镜头。从技术上讲,mk3ple'不合法,因为mk3ple' pure = pure未被满足。但是_3是合法的。这就是为什么我首先提出了over解决方案,因为你想要的并不完全符合镜头模式。我不确定创建一个类型类,其中一些实例是合法的,而其他实例则不合法,是否是一个好主意,但这是你的代码! - user11228628
1
请注意,这不仅仅是mk3ple的问题。任何按照您最初描述的方式行为的镜头或者setter都无法合法。对于setter来说,相关法律是“over l id = id”。但是,如果您的光学元件l将二元组转换为三元组,则id在其上的操作不能是idover l id的输入类型为二元组,但输出却是三元组。 - user11228628
1
此外,对于问题“我应该拼命与类型系统斗争,以便我可以给两个东西取相同的名称”的答案总是:“当然,你应该这样做。可能会出什么问题呢?” - K. A. Buhr
我的期望是编译器/类型系统只要代码能编译通过,就说明它是良好类型化的。那么我在什么意义上“违抗”了类型系统呢?哦对了:GHC编译方法,但你会发现它们无法使用/没有非底部值可以居住推断出的类型。这就是富镜头将“遵守法律”的东西扔掉的原因:EKmett似乎喜欢打破vanLaarhoven定律。所以我将努力找到一个更遵守法律的解决方案。我已经可以做到over l id = id - AntC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接