Как я могу выразить `mapM` с помощью `concat`, используя линзы для объединения результатов операции ввода-вывода?

Я пытаюсь понять, как объединить traverseOf с >>= таким образом, чтобы было возможно следующее.

TLDR; Простым примером на чистом Haskell было бы что-то вроде этого, но с использованием линз глубоко внутри структуры данных.

λ> fmap concat $ mapM ((return :: a -> IO a) . const ["he", "he"]) ["foo", "bar", "baz"]
["he","he","he","he","he","he"]

Вот длинное объяснение с примерами

data Foo = Foo [Bar] deriving Show
data Bar = Baz | Qux Int [String] deriving Show

makePrisms ''Foo
makePrisms ''Bar

items :: [Foo]
items = [Foo [Baz], Foo [Qux 1 ["hello", "world"], Baz]]

-- Simple replacement with a constant value
constReplace :: [Foo]
constReplace = over (traverse._Foo.traverse._Qux._2.traverse) (const "hehe") items
-- λ> constReplace
-- [Foo [Baz],Foo [Qux 1 ["hehe","hehe"],Baz]]

-- Doing IO in order to fetch the new value. This could be replacing file names
-- with the String contents of the files.
ioReplace :: IO [Foo]
ioReplace = (traverse._Foo.traverse._Qux._2.traverse) (return . const "hehe") items
-- λ> ioReplace
-- [Foo [Baz],Foo [Qux 1 ["hehe","hehe"],Baz]]

-- Replacing a single value with a list and concatenating the results via bind
concatReplace :: [Foo]
concatReplace = over (traverse._Foo.traverse._Qux._2) (>>= const ["he", "he"]) items
-- λ> concatReplace
-- [Foo [Baz],Foo [Qux 1 ["he","he","he","he"],Baz]]

-- Same as the previous example, but the list comes from an IO action
concatIoReplace :: IO [Foo]
concatIoReplace = (traverse._Foo.traverse._Qux._2) (return . (>>= const ["he", "he"])) items
-- λ> concatIoReplace
-- [Foo [Baz],Foo [Qux 1 ["he","he","he","he"],Baz]]

Теперь в последнем примере проблема заключается в том, что я немного схитрил, изменив применяемую функцию. В concatReplace я смог использовать >>= (спасибо полезным ребятам на канале #haskell-lens) для реализации функций, подобных concatMap. Но в моем реальном коде у меня есть функция String -> IO [String], которая будет выглядеть примерно так

correctConcatIo :: IO [Foo]
correctConcatIo = (traverse._Foo.traverse._Qux._2) (>>= (return . const ["he", "he"])) items

Но этот пример больше не проверяет тип. Что мне нужно, так это собрать логику из ioReplace и concatReplace таким образом, чтобы я мог применить функцию с типом String -> IO [String] к структуре данных, содержащей [String].


person Jakub Arnold    schedule 01.07.2014    source источник


Ответы (1)


Вы можете заменить строку на [String] только в том случае, если она уже находится в списке (рассмотрите возможность попытки вставить [Int] обратно в _Qux._1), поэтому вам нужно превратить свою функцию в [String]->IO [String] и заменить весь список, используя какой-то подход, как вы уже продемонстрировали:

concatMapM f l = fmap concat (mapM f l)

doIOStuff s = return ['a':s, 'b':s]

concatIO :: IO [Foo]
concatIO = (traverse._Foo.traverse._Qux._2) (concatMapM doIOStuff) items

Вы даже можете скомпоновать этот concatMapM в конце, чтобы получить что-то с типом LensLike, но он недостаточно гибок для использования с большинством комбинаторов линз.

person Brandon    schedule 01.07.2014