如何使用 Lenses 遍历并赋值给 Map 中的部分(但不是全部)元素
How to use Lenses to traverse and assign to some (but not all) elements in a Map
我一直在尝试将镜头和容器一起使用并取得了一些成功,但我在尝试使用 Data.Map 的过滤遍历时遇到了我的理解限制 - 我可以更改地图中的单个实例或遍历所有实例,但我不知道如何对某些可识别的分区(即范围内的键)进行操作。
基本上我正在尝试用地图做一些类似于 Gabriel Gonzalez 优秀镜头教程用列表做的事情 [1]
这是我的代码的工作框架,其中包含 traverseSome
函数,我不知道如何编写注释掉的代码。非常感谢收到任何帮助!
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RankNTypes #-}
import Control.Lens
import Control.Monad.State
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
type CharSet = Set.Set Char
type MapOfSets = Map.Map Int CharSet
data DB = DB { _mos :: MapOfSets } deriving (Show, Eq)
makeLenses ''DB
initDB :: DB
initDB = DB { _mos = Map.fromList (zip [1..5] (repeat Set.empty)) }
add2Map :: Int -> CharSet -> State DB ()
add2Map i cs = mos.ix i %= (Set.union cs)
traverseAll :: Traversal' DB CharSet
traverseAll = mos.traversed
add2MapsAll :: CharSet -> State DB ()
add2MapsAll cs = traverseAll %= (Set.union cs)
-- <problematic part>
{-
traverseSome :: [Int] -> Int -> Traversal' DB MapOfSets
traverseSome ids i = _
add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome ids cs = mos.(traverseSome ids 2) %= (Set.union cs)
-}
-- </problematic part>
main :: IO ()
main = do
let db = initDB
let bar = Set.fromList ['a'..'g'] :: CharSet
let baz = Set.fromList ['f'..'m'] :: CharSet
let quux = Set.fromList ['n'..'z'] :: CharSet
let db2 = execState (add2Map 5 bar) db
let db3 = execState (add2MapsAll baz) db
-- let db4 = execState (add2MapsSome [1,3] quux) db
print db2
print db3
-- print db4
[1] http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html
我假设你的意思是
traverseSome :: [Int] -> Traversal' DB CharSet
这是一个更通用的版本
keys :: Ord k => [k] -> IndexedTraversal' k (Map.Map k a) a
keys ks f m = go ks <&> \m' -> foldr (uncurry M.insert) m m'
where
go [] = pure []
go (i:is) = case Map.lookup i m of
Just a -> (:) . (,) i <$> indexed f i a <*> go is
Nothing -> go is
这与 Data.Vector.Lens
中的 ordinals
非常相似(我的版本没有重复项,因此请确保列表中没有重复项)。 go
遍历索引列表并在地图中查找它们,同时添加索引。 foldr
位遍历已编辑元素的列表,然后 inserts
它们返回到原始地图。
你可以把你的写成
traverseSome :: [Int] -> IndexedTraversal' Int DB CharSet
traverseSome is = mos . keys is
add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome is cs = traverseSome is %= Set.union cs
如果你确实想要
traverseSome :: [Int] -> Lens' DB MapOfSets
这可以写成(注意你不应该向 Map
添加新键,否则你会违反镜头法则)
submap :: Ord k => [k] -> Lens' (Map.Map k a) (Map.Map k a)
submap ks f m = f (Map.fromList as) <&> (<> m)
where as = Maybe.mapMaybe (\i -> (,) i <$> Map.lookup i m) ks
可以用来写keys
(但效率会较低,因为你做了一个中间Map
):
keys :: Ord k => [k] -> IndexedTraversal' k (Map k a) a
keys ks = submap ks . itraversed
编辑:没有中间列表的版本:
keys :: Ord k => [k] -> IndexedTraversal' k (Map.Map k a) a
keys ks f m = go ks
where
go [] = pure m
go (i:is) =
case Map.lookup i m of
Just a -> Map.insert i <$> indexed f i a <*> go is
Nothing -> go is
Map 是一个instance of TraversableWithIndex
, so you can use itraversed
to also traverse the keys. indices
可以用来缩小键的范围。
traverseSome :: [Int] -> Traversal' DB CharSet
traverseSome ids = mos . itraversed . indices (`Set.member` idSet) where
idSet = Set.fromList ids
注意这里的itraversed
和traversed
是不一样的。 traversed
始终按元素的序号位置进行索引,而 itraversed
可能会根据数据结构按各种键类型进行索引。
我一直在尝试将镜头和容器一起使用并取得了一些成功,但我在尝试使用 Data.Map 的过滤遍历时遇到了我的理解限制 - 我可以更改地图中的单个实例或遍历所有实例,但我不知道如何对某些可识别的分区(即范围内的键)进行操作。
基本上我正在尝试用地图做一些类似于 Gabriel Gonzalez 优秀镜头教程用列表做的事情 [1]
这是我的代码的工作框架,其中包含 traverseSome
函数,我不知道如何编写注释掉的代码。非常感谢收到任何帮助!
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RankNTypes #-}
import Control.Lens
import Control.Monad.State
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
type CharSet = Set.Set Char
type MapOfSets = Map.Map Int CharSet
data DB = DB { _mos :: MapOfSets } deriving (Show, Eq)
makeLenses ''DB
initDB :: DB
initDB = DB { _mos = Map.fromList (zip [1..5] (repeat Set.empty)) }
add2Map :: Int -> CharSet -> State DB ()
add2Map i cs = mos.ix i %= (Set.union cs)
traverseAll :: Traversal' DB CharSet
traverseAll = mos.traversed
add2MapsAll :: CharSet -> State DB ()
add2MapsAll cs = traverseAll %= (Set.union cs)
-- <problematic part>
{-
traverseSome :: [Int] -> Int -> Traversal' DB MapOfSets
traverseSome ids i = _
add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome ids cs = mos.(traverseSome ids 2) %= (Set.union cs)
-}
-- </problematic part>
main :: IO ()
main = do
let db = initDB
let bar = Set.fromList ['a'..'g'] :: CharSet
let baz = Set.fromList ['f'..'m'] :: CharSet
let quux = Set.fromList ['n'..'z'] :: CharSet
let db2 = execState (add2Map 5 bar) db
let db3 = execState (add2MapsAll baz) db
-- let db4 = execState (add2MapsSome [1,3] quux) db
print db2
print db3
-- print db4
[1] http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html
我假设你的意思是
traverseSome :: [Int] -> Traversal' DB CharSet
这是一个更通用的版本
keys :: Ord k => [k] -> IndexedTraversal' k (Map.Map k a) a
keys ks f m = go ks <&> \m' -> foldr (uncurry M.insert) m m'
where
go [] = pure []
go (i:is) = case Map.lookup i m of
Just a -> (:) . (,) i <$> indexed f i a <*> go is
Nothing -> go is
这与 Data.Vector.Lens
中的 ordinals
非常相似(我的版本没有重复项,因此请确保列表中没有重复项)。 go
遍历索引列表并在地图中查找它们,同时添加索引。 foldr
位遍历已编辑元素的列表,然后 inserts
它们返回到原始地图。
你可以把你的写成
traverseSome :: [Int] -> IndexedTraversal' Int DB CharSet
traverseSome is = mos . keys is
add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome is cs = traverseSome is %= Set.union cs
如果你确实想要
traverseSome :: [Int] -> Lens' DB MapOfSets
这可以写成(注意你不应该向 Map
添加新键,否则你会违反镜头法则)
submap :: Ord k => [k] -> Lens' (Map.Map k a) (Map.Map k a)
submap ks f m = f (Map.fromList as) <&> (<> m)
where as = Maybe.mapMaybe (\i -> (,) i <$> Map.lookup i m) ks
可以用来写keys
(但效率会较低,因为你做了一个中间Map
):
keys :: Ord k => [k] -> IndexedTraversal' k (Map k a) a
keys ks = submap ks . itraversed
编辑:没有中间列表的版本:
keys :: Ord k => [k] -> IndexedTraversal' k (Map.Map k a) a
keys ks f m = go ks
where
go [] = pure m
go (i:is) =
case Map.lookup i m of
Just a -> Map.insert i <$> indexed f i a <*> go is
Nothing -> go is
Map 是一个instance of TraversableWithIndex
, so you can use itraversed
to also traverse the keys. indices
可以用来缩小键的范围。
traverseSome :: [Int] -> Traversal' DB CharSet
traverseSome ids = mos . itraversed . indices (`Set.member` idSet) where
idSet = Set.fromList ids
注意这里的itraversed
和traversed
是不一样的。 traversed
始终按元素的序号位置进行索引,而 itraversed
可能会根据数据结构按各种键类型进行索引。