关联列表的镜头
Lenses for association list
在 Control.Lens.At
中有 at
个用于 Map
/HashMap
/etc 的镜头。但是对于关联列表类型[(k, v)]
(可转换为地图),是否有类似于at
的镜头?
我不知道有没有为您提供的,但是 at
属于类型类 At
,因此我们当然可以自己编写。为了避免弄脏灵活(并且可能重叠)的实例扩展,我们将在新类型中执行此操作。
newtype AList k v = AList [(k, v)]
首先,我们需要几个家庭实例。
{-# LANGUAGE TypeFamilies #-}
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
这只是定义了 "key" 和 "value" 在我们的新类型中是什么,这很简单。现在,我们需要能够在特定键上读取和写入值。 Haskell 已经为我们提供了一种读取值的方法 (Data.List.lookup
),但是我们必须自己编写写入函数。这里没有花哨的东西或镜头:只有普通的旧 Haskell 滤镜和贴图。
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
现在我们需要编写At
实例,它依赖于Ixed
实例。幸运的是,只要我们实现 At
,lens 库就为 Ixed
提供了默认实现,因此第一个实例声明很简单。
instance Eq k => Ixed (AList k v)
写作at
也相当简单。只要看一下类型,稍微跟着你的鼻子走,你得到的实现就是我们想要的。
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)
我们完成了。现在 at
将适用于 AList
。如果 newtype 包装器困扰你,你可以很容易地创建一个新函数(at'
,如果你愿意的话)为你做 newtype wrapping/unwrapping。
证明此实例满足透镜定律留作 reader 的练习。
完整代码
{-# LANGUAGE TypeFamilies #-}
import Control.Lens.At
import Data.List(lookup)
newtype AList k v = AList [(k, v)]
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
-- Just take the default implementation here.
instance Eq k => Ixed (AList k v)
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)
在 Control.Lens.At
中有 at
个用于 Map
/HashMap
/etc 的镜头。但是对于关联列表类型[(k, v)]
(可转换为地图),是否有类似于at
的镜头?
我不知道有没有为您提供的,但是 at
属于类型类 At
,因此我们当然可以自己编写。为了避免弄脏灵活(并且可能重叠)的实例扩展,我们将在新类型中执行此操作。
newtype AList k v = AList [(k, v)]
首先,我们需要几个家庭实例。
{-# LANGUAGE TypeFamilies #-}
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
这只是定义了 "key" 和 "value" 在我们的新类型中是什么,这很简单。现在,我们需要能够在特定键上读取和写入值。 Haskell 已经为我们提供了一种读取值的方法 (Data.List.lookup
),但是我们必须自己编写写入函数。这里没有花哨的东西或镜头:只有普通的旧 Haskell 滤镜和贴图。
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
现在我们需要编写At
实例,它依赖于Ixed
实例。幸运的是,只要我们实现 At
,lens 库就为 Ixed
提供了默认实现,因此第一个实例声明很简单。
instance Eq k => Ixed (AList k v)
写作at
也相当简单。只要看一下类型,稍微跟着你的鼻子走,你得到的实现就是我们想要的。
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)
我们完成了。现在 at
将适用于 AList
。如果 newtype 包装器困扰你,你可以很容易地创建一个新函数(at'
,如果你愿意的话)为你做 newtype wrapping/unwrapping。
证明此实例满足透镜定律留作 reader 的练习。
完整代码
{-# LANGUAGE TypeFamilies #-}
import Control.Lens.At
import Data.List(lookup)
newtype AList k v = AList [(k, v)]
type instance IxValue (AList k v) = v
type instance Index (AList k v) = k
replaceAt :: Eq k => k -> Maybe v -> AList k v -> AList k v
replaceAt k Nothing (AList m) = AList $ filter (\(k', _) -> k /= k') m
replaceAt k (Just v) (AList m) =
case lookup k m of
Nothing ->
-- Not present in the list; add it
AList ((k, v) : m)
Just _ ->
-- Present; replace it
AList $ map (\(k', v') -> if k == k' then (k', v) else (k', v')) m
-- Just take the default implementation here.
instance Eq k => Ixed (AList k v)
instance Eq k => At (AList k v) where
at k f (AList m) = fmap (\v' -> replaceAt k v' (AList m)) $ f (lookup k m)