Не можете выполнить ввод / вывод в фолдере? - PullRequest
17 голосов
/ 17 мая 2011

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

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

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

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

Итак, почему другие elemnts не появляются при попытке выполнить I /O?Это то, как работает foldr, или я пропускаю какой-то тонкий ленивый причуд?

Ответы [ 4 ]

23 голосов
/ 17 мая 2011

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

В вашем случае вы отбрасываете ввод "результата" функции-накопителя - обратите внимание, что аргумент 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_.

11 голосов
/ 17 мая 2011

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

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
6 голосов
/ 17 мая 2011

Смешивать ввод-вывод и печатать так довольно плохо, так как насчет вывода ввода-вывода:

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

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

Так что следите за этим:

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

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

3 голосов
/ 17 мая 2011

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

...