问题 Haskell如何使用奇怪的参数顺序实现“使用函数组合构成镜头”?


我一直在读 一个wreq教程

镜头提供了一种聚焦于Haskell值的一部分的方法。对于   例如, Response 类型有一个 responseStatus 镜头,哪个   重点关注服务器返回的状态信息。

ghci> r ^. responseStatus
Status {statusCode = 200, statusMessage = "OK"}

^. operator将一个值作为其第一个参数,一个镜头作为它的   第二,并返回由镜头聚焦的值的部分。

我们使用功能组合构成镜头,这使我们能够   容易专注于深层嵌套结构的一部分。

ghci> r ^. responseStatus . statusCode
200

我无法想出如何使用这个参数顺序完成的函数组合可以按顺序处理嵌套结构。

看: r ^. responseStatus . statusCode 可能是 r ^. (responseStatus . statusCode) 要么 (r ^. responseStatus) . statusCode

在第一个说我们构造一个函数 第一 对待 statusCode (从记录中获取它 Status? - 因为我可以从显示的值中推断出来 Status {statusCode = 200, statusMessage = "OK"}),然后传递给 responseStatus 必须处理响应状态。所以,反过来说:实际上,状态代码是响应状态的一部分。

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


9266
2018-03-08 17:18


起源

statusCode 必须是镜头,而不是记录场选择器。我猜他们必须隐藏场选择器并输出一个同名的镜头;如果你问我,你会很困惑。 (或者写了一个自定义的Show实例。) - Reid Barton
@ReidBarton这一定是谜题的一部分。由于我对镜头知之甚少,我的问题也是一个基本思想的问题:如何使用正常的功能组合以相反的顺序访问结构? (这让我想起了继续传球的风格。) - imz -- Ivan Zakharyaschev
这基本上是正确的。镜片有点像CPS计算的一种,所以它们作为副作用,翻转功能组合。 - J. Abrahamson


答案:


正确阅读 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)]

first 和 map 将作用于结构一部分的功能转换为作用于整个结构的功能。在 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 比特现在。如果我们略微斜视,这类似于 map 和 first。它的发生使得镜头还将作用于结构部件的功能转换为作用于整个结构的功能。在上面的签名 s 表示整个结构和 a 表示它的一部分。由于我们的输入功能可以改变类型 a 至 b(如 a -> f b),我们也需要 t 参数,大致意思是“的类型 s 我们改变之后 a 至 b 在里面”。

statusCode 是一个转换功能的镜头 Int 一个作用于...的功能 Status

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

responseStatus 转换作用于a的函数 Status 一个作用于...的功能 Response

responseStatus :: Functor f => (Status -> f Status) -> (Response -> f Response)

的类型 responseStatus . statusCode 遵循与我们见过的相同的模式 map . first

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

还有待观察 ^. 作品。它与镜头的核心机制和魔力密切相关;我不会在这里重申,因为有很多关于它的着作。有关介绍,我建议您查看 这个 和 这个,你也可以看 这出色的视频。


12
2018-03-08 18:25