如何实现Haskell中“使用函数组合来组合镜头”这种奇怪的参数顺序?

12

我一直在阅读wreq教程

透镜提供了一种聚焦于Haskell值的一部分的方法。例如,Response类型具有一个responseStatus透镜,它聚焦于服务器返回的状态信息。

ghci> r ^. responseStatus
Status {statusCode = 200, statusMessage = "OK"}
< p > ^ . 运算符以值作为其第一个参数,以镜头作为其 第二个参数,并返回由该镜头聚焦的值的部分。

我们使用函数组合来组合镜头,这使我们可以轻松地聚焦于深度嵌套结构的一部分。

ghci> r ^. responseStatus . statusCode
200

我想不出如何使用这个参数顺序进行函数组合,以便以那种顺序处理嵌套结构。

看看:r ^. responseStatus . statusCode 既可以是 r ^. (responseStatus . statusCode) ,也可以是 (r ^. responseStatus) . statusCode

在第一种情况下,我们构造一个函数,首先处理 statusCode(从记录中获取它? -- 从所显示的值Status {statusCode = 200, statusMessage = "OK"}中我可以推断),然后将其传递给必须处理响应状态的responseStatus。 因此,实际上,状态码是响应状态的一部分,顺序刚好相反。

对我来说,第二种阅读也没有意义,因为它首先处理状态码。


3
statusCode必须是一个光学器,而不是记录字段选择器。我猜他们可能隐藏了字段选择器并导出了同名的光学器;如果你问我,这很令人困惑。(或者编写了自定义Show实例。) - Reid Barton
@ReidBarton 那一定是谜题的一部分。由于我对透镜知之甚少,我的问题也是关于基本思想的问题:如何使用正常的函数组合来倒序访问结构?(这让我想起了一点延续传递风格。) - imz -- Ivan Zakharyaschev
4
基本上没错。镜头有点像一种 CPS 计算,所以它们作为副作用翻转函数组合。 - J. Abrahamson
1个回答

14

r ^. responseStatus . statusCode 的正确阅读方式是 r ^. (responseStatus . statusCode)。这很自然,因为当两个参数应用于函数组合时,函数组合返回一个函数,因此 (r ^. responseStatus) . statusCode 必须返回一个函数,而不是可以打印出的任何值。

这仍然不解释为什么镜片以“错误”的顺序组成。由于镜头的实现有点神奇,让我们看一个更简单的例子。

first 是一个函数,它映射到一对中的第一个元素:

first :: (a -> b) -> (a, c) -> (b, c)
first f (a, b) = (f a, b)
map . first 是什么意思?first 接受一个作用于第一个元素的函数,并返回一个作用于一对元素的函数。如果我们将类型括在括号中,这一点就更加明显了:
first :: (a -> b) -> ((a, c) -> (b, c))

此外,请回忆一下map的类型:
map :: (a -> b) -> ([a] -> [b])

map接收作用于元素的函数,并返回作用于列表的函数。现在,f . g的工作方式是先应用g,然后将结果传递给f。因此,map . first接收作用于某些元素类型的函数,将其转换为作用于对的函数,然后再将其转换为作用于对列表的函数。

(map . first) :: (a -> b) -> [(a, c)] -> [(b, c)]
< p > firstmap 都将作用于结构的部分的函数转化为作用于整个结构的函数。在 map . first 中,first 的整个结构成为了 map 的焦点。

(map . first) (+10) [(0, 2), (3, 4)] == [(10, 2), (13, 4)]

现在看看镜头的类型:
type Lens = forall f. Functor f => (a -> f b) -> (s -> f t)

暂时忽略Functor部分。如果我们微微眯起眼睛看,这就像是mapfirst的类型。而且透镜也会将作用于结构部分的函数转换为作用于整个结构的函数。在上面的签名中,s表示整个结构,a表示其部分。由于我们的输入函数可以将a的类型更改为b(如a-> f b所示),因此我们还需要t参数,大致表示“在其中将a更改为bs的类型”。

statusCode是一个透镜,它将作用于Int的函数转换为作用于Status的函数:

statusCode :: Functor f => (Int -> f Int) -> (Status -> f Status)

responseStatus 将作用于 Status 的函数转换为作用于 Response 的函数:

responseStatus :: Functor f => (Status -> f Status) -> (Response -> f Response)
< p > responseStatus.statusCode 的类型遵循与我们在map.first中看到的相同模式:

responseStatus . statusCode :: Functor f => (Int -> f Int) -> (Response -> f Response)

目前还不清楚^.是如何工作的。它与镜头的核心机制和魔法紧密相连; 我不会在这里重申,因为已经有很多关于它的文章了。对于介绍,我建议看看这篇文章这篇文章,您也可以观看这个优秀的视频。


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