【问题标题】: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)}

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

【问题讨论】:

    标签: haskell haskell-lens lenses


    【解决方案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。顺便说一句,标题名称不区分大小写,因此它会自动进行不区分大小写的匹配
    • 哦,你是对的。我已经删除了关于进行不区分大小写匹配的评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-27
    • 2018-03-16
    • 2011-11-13
    相关资源
    最近更新 更多