Как я могу использовать `over` из Control.Lens, но выполнить монадическое действие и собрать результаты?

#haskell #haskell-lens #lenses

#haskell #haskell-lens #линзы

Вопрос:

Проблема довольно проста. У меня есть структура, которая выглядит примерно так

 data Foo = Foo [Bar]
data Bar = Boo | Moo Item Int
data Item = Item String Int
  

и у меня есть объектив для изменения содержимого Item s внутри структуры данных, например, такой

 let foos = [Foo [Boo, Moo (Item "bar" 20) 10]]
over (traverse._Foo._Moo._1._Item._1) ("foo"   ) foos

-- which yields [Foo [Boo, Moo (Item "foobar" 20) 10]]
  

Структура здесь не важна, я просто хотел показать пример, в котором используются призмы и что-то глубоко вложенное.

Теперь проблема в том, что мне нужно, чтобы функция, переданная в over быть String -> IO String , а не просто String -> String . Аналогично тому, что я ищу здесь, что-то вроде mapM , но с линзами. Возможно ли сделать что-то подобное?

Ответ №1:

Объектив обеспечивает traverseOf функцию, которая точно такая же, mapM но требует подобия линзы (требуется обход, который включает линзы и грунтовки), над которым вы хотите map .

 traverseOf :: Functor f => Iso s t a b       -> (a -> f b) -> s -> f t
traverseOf :: Functor f => Lens s t a b      -> (a -> f b) -> s -> f t
traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
  

Итак, для вашего примера вы можете просто использовать:

 traverseOf (traverse._Foo._Moo._1._Item._1) (... expression of type String -> IO String ...) foos
  

Существует также операторская версия traverseOf , называемая %%~ .


Если вы немного знакомы с представлением линз в библиотеке линз, вы могли бы заметить это traverseOf = id ! Итак, обладая этими знаниями, вы можете переписать пример, чтобы просто:

 (traverse._Foo._Moo._1._Item._1) (... expression of type String -> IO String ...) foos
  

(Вы даже использовали traverse который просто mapM для построения обхода! линзы / грунтовки такие же traverse , но более специфические.)

Но это всего лишь отступление, которое вы, тем не менее, можете захотеть использовать traverseOf для наглядности.