Я не вижу способа написать это как простую композицию комбинаторов линз, но это обходной путь, который вы можете написать с нуля. Он должен либо проходить через все значения ключей "c"
, если каждая карта содержит такой ключ, либо обходить без значений.
Мы можем начать с вспомогательной функции, чтобы «возможно» обновить карту новым значением ключа, сбой в монаде Maybe
, если ключ не существует. По причинам, которые станут очевидными, мы хотим, чтобы обновление происходило в произвольном функторе. То есть нам нужна функция:
maybeUpdate :: (Functor f, Ord k) => k -> (v -> f v) -> Map k v -> Maybe (f (Map k v))
Эта подпись понятна? Проверяем на ключ k
. Если ключ найден, мы вернем Just
обновленную карту с соответствующим значением ключа v
, обновленным в функторе f
. В противном случае , если ключ не найден, мы возвращаем Nothing
. Мы можем написать это довольно четко в нотации монады, хотя нам нужно расширение ApplicativeDo
, если мы хотим использовать ограничение Functor f
:
maybeUpdate :: (Functor f, Ord k) => k -> (v -> f v) -> Map k v -> Maybe (f (Map k v))
maybeUpdate k f m = do -- in Maybe monad
v <- m ^. at k
return $ do -- in "f" functor
a <- f v
return $ m & at k .~ Just a
В качестве альтернативы, эти "действия monadi c" действительно только действия функтора, поэтому можно использовать это определение:
maybeUpdate' k f m =
m ^. at k <&> \v -> f v <&> \a -> m & at k .~ Just a
Это сложная часть. Теперь, обход довольно прост. Мы начинаем с подписи:
traverseAll :: (Ord k) => k -> Traversal' [Map k v] v
traverseAll k f maps =
Идея состоит в том, что этот обход начинается с обхода списка карт через аппликатив Maybe
с использованием помощника maybeUpdate
:
traverse (maybeUpdate k f) maps :: Maybe [f (Map k v)]
Если этот обход успешен (возвращает Just
список), то все ключи были найдены, и мы можем выполнить последовательность f
аппликативных действий:
sequenceA <$> traverse (maybeUpdate k f) maps :: Maybe (f [Map k v])
Теперь мы просто используем maybe
, чтобы вернуть исходный список при неудачном прохождении:
traverseAll k f maps = maybe (pure maps) id (sequenceA <$> traverse (maybeUpdate k f) maps)
Теперь с:
y :: [Map String Int]
y = [
fromList([("c", 1 ::Int)]),
fromList([("c", 5)]),
fromList([("d", 20)])
]
y2 :: [Map String Int]
y2 = [
fromList([("c", 1 ::Int)]),
fromList([("c", 5)]),
fromList([("d", 20),("c",6)])
]
у нас есть:
> y & traverseAll "c" %~ (1000*)
[fromList [("c",1)],fromList [("c",5)],fromList [("d",20)]]
> y2 & traverseAll "c" %~ (1000*)
[fromList [("c",1000)],fromList [("c",5000)],fromList [("c",6000),("d",20)]]
Полное раскрытие: я не смог построить traverseAll
вот так с нуля. Я начал с гораздо более глупого «обхода» в неявной аппликативной идентификации:
traverseAllC' :: (Int -> Int) -> [Map String Int] -> [Map String Int]
traverseAllC' f xall = maybe xall id (go xall)
where go :: [Map String Int] -> Maybe [Map String Int]
go (x:xs) = case x !? "c" of
Just a -> (Map.insert "c" (f a) x:) <$> go xs
Nothing -> Nothing
go [] = Just []
, и как только я его запустил, я упростил его, сделал Identity
явным:
traverseAllC_ :: (Int -> Identity Int) -> [Map String Int] -> Identity [Map String Int]
и преобразовал его в общее аппликативное значение.
В любом случае, вот код:
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE RankNTypes #-}
import Data.Map (Map, fromList)
import Control.Lens
y :: [Map [Char] Int]
y = [
fromList([("c", 1 ::Int)]),
fromList([("c", 5)]),
fromList([("d", 20)])
]
y2 :: [Map [Char] Int]
y2 = [
fromList([("c", 1 ::Int)]),
fromList([("c", 5)]),
fromList([("d", 20),("c",6)])
]
traverseAll :: (Ord k) => k -> Traversal' [Map k v] v
traverseAll k f maps = maybe (pure maps) id (sequenceA <$> traverse (maybeUpdate k f) maps)
maybeUpdate :: (Functor f, Ord k) => k -> (v -> f v) -> Map k v -> Maybe (f (Map k v))
maybeUpdate k f m = do
v <- m ^. at k
return $ do
a <- f v
return $ m & at k .~ Just a
maybeUpdate' :: (Functor f, Ord k) => k -> (v -> f v) -> Map k v -> Maybe (f (Map k v))
maybeUpdate' k f m =
m ^. at k <&> \v -> f v <&> \a -> m & at k .~ Just a
main = do
print $ y & traverseAll "c" %~ (1000*)
print $ y2 & traverseAll "c" %~ (1000*)