Использование эффекта Traversable [] и Applicative Maybe в библиотеке линз - PullRequest
5 голосов
/ 13 марта 2020

У меня следующая структура:

y = [
  fromList([("c", 1 ::Int)]),
  fromList([("c", 5)]),
  fromList([("d", 20)])
  ]

Я могу использовать это для обновления каждого "c":

y & mapped . at "c" . mapped  %~ (+ 1)
-- [fromList [("c",2)], fromList [("c",6)], fromList [("d",20)]]

Так что третья запись в основном просто игнорируется. Но я хочу, чтобы операция провалилась.

Только обновлять, если все карты содержат ключ "c".

Итак, я хочу:

y & mysteryOp
-- [fromList [("c",1)], fromList [("c",5)], fromList [("d",20)]]
-- fail because third entry does not contain "c" as key

Я думаю, что я знаю, какие функции использовать здесь:

over
-- I want to map the content of the list

mapped
-- map over the structure and transform to [(Maybe Int)]

traverse
-- I need to apply the operation, which will avoid 

at "c"
-- I need to index into the key "c"

Я просто не знаю, как их объединить

Ответы [ 2 ]

2 голосов
/ 14 марта 2020

Вот несколько альтернативных подходов, когда вам нравятся линзы;

Использование лени для отсрочки принятия решения о внесении изменений,

f y = res
  where (All c, res) = y 
                     & each %%~ (at "c" %%~ (Wrapped . is _Just &&& fmap (applyWhen c succ)))

Или предварительное решение о целесообразности изменения,

f' y = under (anon y $ anyOf each (nullOf $ ix "c")) (mapped . mapped . ix "c" +~ 1) y
1 голос
/ 14 марта 2020

Я не вижу способа написать это как простую композицию комбинаторов линз, но это обходной путь, который вы можете написать с нуля. Он должен либо проходить через все значения ключей "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*)
...