使用镜头更新嵌套数据结构

Posted

技术标签:

【中文标题】使用镜头更新嵌套数据结构【英文标题】:Updating a nested data structure using lenses 【发布时间】:2020-12-13 12:08:55 【问题描述】:

我目前正在尝试使用镜头使我的部分代码更简洁。特别是,我有一个HTTP Request,我想将标题的值替换为名称Private-Header

我设法编写了更新RequestHeaders的函数:

updateHeaders :: RequestHeaders -> RequestHeaders
updateHeaders headers = headers & traverse . filtered (\header -> fst header == "Private-Header") %~ set _2 "xxxxxx"

但是,我正在努力想出一个从请求中提取标头并更新它们的函数。如果没有镜头,它可能看起来像这样:

updateRequest :: Request -> Request
updateRequest req = req requestHeaders = updateHeaders (requestHeaders req)

有没有办法使用镜头来实现这个功能?

【问题讨论】:

【参考方案1】:

当然。首先,您需要一个表示RequestHeaders 对象内"Private-Header" 标头值的光学元件。一个合理的候选者是遍历,它允许一种类型在另一种类型中出现零次或多次。 (通常情况下,您只有零个或一个私有标头,但 RequestHeader 类型没有任何基本功能可以防止两个或多个具有相同名称的标头,因此遍历似乎是最安全的选择。)

此光学元件的适当类型是:

privateHeader :: Traversal' RequestHeaders ByteString

您已经完成了在updateHeaders 中定义此光学器件的大部分工作,您只需要重新排列这些部件。表达式:

traverse . filtered (\header -> fst header == "Private-Header")

是一种从RequestHeader 中提取匹配Header 值的光学元件。这是一个有效的遍历,只要你不使用它来修改key和破坏过滤,所以我们可以直接用镜头_2组合它来创建一个从type Header = (ByteString, ByteString)中提取header值的新遍历:

privateHeader = traverse . filtered (\header -> fst header == "Private-Header") . _2

顺便说一句,这个新的遍历也让我们能够简化updateHeaders 的实现。

updateHeaders :: RequestHeaders -> RequestHeaders
updateHeaders = set privateHeader "xxxxxx"

其次,我们需要一个表示RequestRequestHeaders 字段值的光学元件。您可以使用lens 函数构建一个:

headers :: Lens' Request RequestHeaders
headers = lens getter setter
  where getter = requestHeaders
        setter req hdrs = req  requestHeaders = hdrs 

现在,您可以编写headersprivateHeaders 来创建一个新的遍历:

privateHeaderInRequest :: Traversal' Request ByteString
privateHeaderInRequest = headers . privateHeader

updateRequest 可以实现为:

updateRequest :: Request -> Request
updateRequest = set (headers . privateHeader) "xxxxxx"

完整代码:

-# LANGUAGE OverloadedStrings #-

import Control.Lens
import Network.HTTP.Client
import Network.HTTP.Types
import Data.ByteString (ByteString)

privateHeader :: Traversal' RequestHeaders ByteString
privateHeader = traverse . filtered (\header -> fst header == "Private-Header") . _2

updateHeaders :: RequestHeaders -> RequestHeaders
updateHeaders = set privateHeader "xxxxxx"

headers :: Lens' Request RequestHeaders
headers = lens getter setter
  where getter = requestHeaders
        setter req hdrs = req  requestHeaders = hdrs 

updateRequest :: Request -> Request
updateRequest = set (headers . privateHeader) "xxxxxx"

main = do
  request <- parseRequest "http://localhost:8888/"
  -- could use "headers" lens to set this, but let's do it manually
  -- for clarity...
  let request' = request  requestHeaders = [("Private-Header","hello"),
                                             ("Other-Header","goodbye")] 
  print $ requestHeaders (updateRequest request')

【讨论】:

好的,所以我错过的要点是我构建了一个指向我想要更新的字段的单个光学元件,最后我在它上面调用了一次set。顺便说一句,标题名称不区分大小写,因此它会自动进行不区分大小写的匹配 哦,你是对的。我已经删除了关于进行不区分大小写匹配的评论。

以上是关于使用镜头更新嵌套数据结构的主要内容,如果未能解决你的问题,请参考以下文章

无法使用镜头 kudu sink 连接器将数据从 kafka 主题插入或更新到 kudu 表

尝试对其键的子集进行镜头/遍历映射多重更新

使用嵌套数据更新 CoreData 对象时,RestKit 验证失败(Cocoa 错误 1550)

Spring JPA 更新不适用于嵌套对象

Mongoose:通过 findOneAndUpdate 查询使用嵌套对象数组的总和更新父子数据属性不起作用

Mongoose findOneAndUpdate:创建然后更新嵌套数组