Подтвердить что ты не робот

Не удается выполнить ввод-вывод в foldr?

У меня есть структура Data.Map, которая отображает String в Strings s. По какой-то причине я хочу распечатать содержимое карты в формате key: value с помощью foldrWithKey, например:

M.foldrWithKey (\k v b -> putStrLn (k++": "++v++"\n")) (return ()) data

Однако на выходе появляется только первый элемент карты (хотя карта имеет более одного элемента). Но когда я пытаюсь создать список с помощью foldrWithKey, а затем распечатаю его, все элементы появятся:

print (M.foldrWithKey (\k v b -> k:b) [] data)

Итак, почему другие элементы не появляются при попытке выполнить ввод-вывод? Так ли работает складной склад или есть какая-то тонкая ленивая io-связанная причуда, которую мне не хватает?

4b9b3361

Ответ 1

Это из-за того, как работает правая складка, да. Функция сгиба - это накопление: на каждом шаге, учитывая один элемент (в данном случае ключ и значение) и накопленный результат остальной части данных, он объединяет их в один результат. Сложение в целом делает это рекурсивно, чтобы суммировать весь набор данных.

В вашем случае вы отбрасываете "результат" функции аккумулятора - обратите внимание, что аргумент b никогда не используется. Имейте в виду, что IO a - это не просто значение типа a с добавленным дополнительным нежелательным файлом, то на самом деле он представляет собой вычисление для создания a, и это вычисление будет выполняться только путем объединения его с другим вычислений как часть конечного значения функции main (или в GHCi выражения, которое оценивается).

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

От того, как вы сформулировали вопрос, я предполагаю, что вам все еще удобнее программировать в стиле императива, чем функциональный стиль Haskell. Очевидно, что на императивном языке, где печать фактически "происходит" во время складки в каком-то значимом смысле, было бы разумно предположить, что накопленная ценность бесполезна. Если это помогает, подумайте об этом скорее как о метапрограмме; вы на самом деле не пишете цикл для печати значений, вы создаете настоящую программу, которая выполняет фактическую печать, и, отбрасывая накопленное значение, вы в основном отбрасываете все, кроме первых строк развернутого цикла, чтобы злоупотреблять аналогия плохо.

Во всяком случае, то, что вы, вероятно, хотите в этом случае, - принять действие "распечатать остальную часть данных", параметр b и объединить его с действием putStrLn ... с помощью (>>), оператора что в основном означает "выполнить первое действие, проигнорировать результат, выполнить второе". Это довольно прямой перевод "инструкции печати по требованию" в цикле.


Кроме того, хотя я понимаю, что это касается цели полностью, я бы, вероятно, избегал смешивания форматирования и печати таким образом. На мой взгляд, кажется более аккуратным форматировать каждую пару ключ/значение отдельно в список, а затем просто mapM_ putStrLn над этим.

mapM_ - функция высшего порядка, которая описывает суть того, что вы здесь делаете; заданный список какого-либо типа a и функция, которая превращает a в какое-либо действие IO, оно применяет функцию к каждому элементу и запускает результирующий список действий в порядке. mapM_ имеет тип Monad m => (a -> m b) -> [a] -> m (), который сначала кажется загадочным, но одна из приятных вещей о Haskell заключается в том, что как только вы привыкнете к чтению подписей типа, тип mapM_ не только понятен с первого взгляда, но и почти самодокументирован в том, что существует только одна разумная вещь для функции с этим типом и что именно то, что делает сама mapM_.

Ответ 2

Вот более ясный пример того, что происходит, без использования ввода-вывода.

foldr (\x b -> x) 9 [8,7,6,5,4,3,2,1,0]

Это выражение возвращает 8, глава списка. Куда отправляется остальная часть списка? Ну, результат обработки остальной части списка передается в 'b', который не используется, поэтому остальная часть списка просто игнорируется.

То же самое происходит и в вашем случае. Путем игнорирования аккумулятора 'b' вы создаете действие ввода-вывода, которое использует только один элемент карты. В основном вы сказали: "распечатать карту, распечатать ее первый ключ и значение". Вы должны сказать: "Чтобы распечатать карту, распечатать ее первый ключ и значение, а затем распечатать оставшуюся часть карты". Для этого вам необходимо настроить содержимое переменной "b" для запуска после вызова putStrLn:

M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d

Ответ 3

Это несколько плохая форма для смешения IO и довольно печатной печати, поэтому, как насчет плавания IO out:

> putStr $ foldrWithKey (\k v b -> b ++ k ++ ": "++v++"\n") [] m

Теперь, что касается того, почему ваш код не работает, подумайте о том, как складывается ваша сводка: последовательность операторов печати в параметре b. Однако вы выбрасываете b каждый раз вокруг цикла!

Так что следите за ней:

> foldrWithKey (\k v b -> putStrLn (k++": "++v) >> b) (return ()) m   

Урок, не выбрасывайте свои аккумуляторы.

Ответ 4

когда вы складываете с помощью (\ kvb → putStrLn (k ++ ":" ++ v ++ "\n" )), вы не используете нигде, поэтому все, что у вас осталось, - это последний IO(), который остается в складке. Поэтому он печатает первое значение. Вы можете предотвратить это, свернув с помощью (\ k v b → putStrLn (k ++ ":" ++ v ++ "\n" ) → b).