使用镜头替换(键,值)列表的特定元素
Use a lens to replace a specific element of a (key,value) list
我想使用 Kmett 的 lens library 访问特定键下的(键,值)列表的元素。换句话说,我想用更惯用且可能更短的代码替换此代码:
type Headers = [ (ByteString, ByteString) ]
headerLens ::
Functor f =>
ByteString ->
(Maybe ByteString -> f (Maybe ByteString)) ->
Headers ->
f Headers
headerLens header_name f headers
| old_header_value <- fetchHeader headers header_name = fmap
(\ new_header_value ->
replaceHeaderValue
headers
header_name
new_header_value
)
(f old_header_value )
其中支持函数定义如下:
-- | Looks for a given header and returns the value, if any
fetchHeader :: Headers -> ByteString -> Maybe ByteString
fetchHeader headers header_name =
snd <$> find ( \ x -> fst x == header_name ) headers
-- | replaceHeaderValue headers header_name maybe_header_value looks for
-- header_name. If header_name is found and maybe_header_value is nothing, it
-- returns a new headers list with the header deleted. If header_name is found
-- and header_value is Just new_value, it returns a new list with the header
-- containing the new value. If header_name is not in headers and maybe_header_value
-- is Nothing, it returns the original headers list. If header_name is not in headers
-- and maybe_header_value is Just new_value, it returns a new list where the last element
-- is (header_name, new_value)
replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers
replaceHeaderValue headers header_name maybe_header_value =
disect id headers
where
disect builder [] = case maybe_header_value of
Nothing -> headers
Just new_value -> builder $ (header_name, new_value):[]
disect builder ( el@(hn,hv) : rest)
| hn /= header_name =
disect
(\ constructed_list -> builder $ el:constructed_list )
rest
| otherwise = case maybe_header_value of
Nothing -> builder rest
Just new_value -> builder $ (hn, new_value):rest
好吧,如果您使用 Data.Map.Map
作为您的结构(应该是一个非常简单的重构),您将不必自己复制所有这些工作:
import qualified Data.Map as M
import Control.Lens
type Headers = M.Map ByteString ByteString
fetchHeader :: Headers -> ByteString -> Maybe ByteString
fetchHeader = flip M.lookup
replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers
replaceHeaderValue headers header_name maybe_header_value
= M.alter (const maybe_header_value) header_name headers
那么您可以保持 headerLens
不变。或者你可以查看类似
的内容
headerLens name = lens (M.lookup name) (\hs mhv -> M.alter (const mhv) name hs)
它根本不需要支持函数,并且可以有一个非常通用的签名来处理更多类型的 Map
s。用法示例:
> let hs = M.fromList [("foo", "bar")]
> hs ^. headerLens "foo"
Just "bar"
> hs ^. headerLens "baz"
Nothing
> headerLens "foo" .~ Just "baz" $ hs
fromList [("foo", "baz")]
> headerLens "foo" .~ Nothing $ hs
fromList []
> headerLens "qux" .~ Just "baz" $ hs
fromList [("foo", "bar"), ("qux", "baz")]
> headerLens "qux" .~ Nothing $ hs
fromList [("foo", "bar")]
但是,这不会保留元素的顺序,这对您来说可能是个问题。那里可能有一个有序的地图,类似于 Python 的 OrderedDict
,但我以前没有在 Haskell 中使用过它。
我想使用 Kmett 的 lens library 访问特定键下的(键,值)列表的元素。换句话说,我想用更惯用且可能更短的代码替换此代码:
type Headers = [ (ByteString, ByteString) ]
headerLens ::
Functor f =>
ByteString ->
(Maybe ByteString -> f (Maybe ByteString)) ->
Headers ->
f Headers
headerLens header_name f headers
| old_header_value <- fetchHeader headers header_name = fmap
(\ new_header_value ->
replaceHeaderValue
headers
header_name
new_header_value
)
(f old_header_value )
其中支持函数定义如下:
-- | Looks for a given header and returns the value, if any
fetchHeader :: Headers -> ByteString -> Maybe ByteString
fetchHeader headers header_name =
snd <$> find ( \ x -> fst x == header_name ) headers
-- | replaceHeaderValue headers header_name maybe_header_value looks for
-- header_name. If header_name is found and maybe_header_value is nothing, it
-- returns a new headers list with the header deleted. If header_name is found
-- and header_value is Just new_value, it returns a new list with the header
-- containing the new value. If header_name is not in headers and maybe_header_value
-- is Nothing, it returns the original headers list. If header_name is not in headers
-- and maybe_header_value is Just new_value, it returns a new list where the last element
-- is (header_name, new_value)
replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers
replaceHeaderValue headers header_name maybe_header_value =
disect id headers
where
disect builder [] = case maybe_header_value of
Nothing -> headers
Just new_value -> builder $ (header_name, new_value):[]
disect builder ( el@(hn,hv) : rest)
| hn /= header_name =
disect
(\ constructed_list -> builder $ el:constructed_list )
rest
| otherwise = case maybe_header_value of
Nothing -> builder rest
Just new_value -> builder $ (hn, new_value):rest
好吧,如果您使用 Data.Map.Map
作为您的结构(应该是一个非常简单的重构),您将不必自己复制所有这些工作:
import qualified Data.Map as M
import Control.Lens
type Headers = M.Map ByteString ByteString
fetchHeader :: Headers -> ByteString -> Maybe ByteString
fetchHeader = flip M.lookup
replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers
replaceHeaderValue headers header_name maybe_header_value
= M.alter (const maybe_header_value) header_name headers
那么您可以保持 headerLens
不变。或者你可以查看类似
headerLens name = lens (M.lookup name) (\hs mhv -> M.alter (const mhv) name hs)
它根本不需要支持函数,并且可以有一个非常通用的签名来处理更多类型的 Map
s。用法示例:
> let hs = M.fromList [("foo", "bar")]
> hs ^. headerLens "foo"
Just "bar"
> hs ^. headerLens "baz"
Nothing
> headerLens "foo" .~ Just "baz" $ hs
fromList [("foo", "baz")]
> headerLens "foo" .~ Nothing $ hs
fromList []
> headerLens "qux" .~ Just "baz" $ hs
fromList [("foo", "bar"), ("qux", "baz")]
> headerLens "qux" .~ Nothing $ hs
fromList [("foo", "bar")]
但是,这不会保留元素的顺序,这对您来说可能是个问题。那里可能有一个有序的地图,类似于 Python 的 OrderedDict
,但我以前没有在 Haskell 中使用过它。