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

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

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

и у меня есть линза для изменения содержимого Items внутри структуры данных, такой как эта

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 для ясности.

Другие вопросы по тегам