Haskell - проблема с красивой печатью списка

Я новичок в haskell, и я прочитал и переварил Учите вас A Haskell For Great Good, опробовав пару вещей путь. Для моего первого проекта я хотел попробовать классику: FizzBuzz. Поэтому я придумал следующий код:

import System.IO

fizzBuzz :: (Integral a) => a -> String
fizzBuzz num
    | fizz && buzz = "FizzBuzz"
    | fizz = "Fizz"
    | buzz = "Buzz"
    | otherwise = show num
    where fizz = num `mod` 3 == 0
          buzz = num `mod` 5 == 0

main = print $ map fizzBuzz [1..100]

Работал отлично, за исключением того, что я получил довольно плотный список, который трудно было прочитать. Поэтому я попробовал эту основную функцию:

main = map putStrLn $ map fizzBuzz [1..100]

И это дает мне ошибку Couldn't match expected type 'IO t' against inferred type '[IO ()]'. Я пробовал полдюжины вещей, и ничто из этого не помогло. Какой правильный способ делать то, что я пытаюсь сделать?

Ответ 1

map :: (a -> b) -> [a] -> [b]
putStrLn :: Show a => a -> IO ()
map putStrLn :: Show a => [a] -> [IO ()]

У вас есть список действий IO ().

main :: IO ()

Вам нужно объединить их в одно действие IO ().

Что вы хотите сделать, так это выполнить каждое из этих действий IO () в sequence/sequence_:

sequence :: Monad m => [m a] -> m [a]
sequence_ :: Monad m => [m a] -> m ()

Для удобства mapM/mapM_ будет отображать функцию над списком и упорядочить полученные монадические результаты.

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

Итак, ваш фиксированный код будет выглядеть так:

main = mapM_ putStrLn $ map fizzBuzz [1..100]

Хотя я бы, наверное, написал его вот так:

main = mapM_ (putStrLn . fizzBuzz) [1..100]

Или даже это:

main = putStr $ unlines $ map fizzBuzz [1..100]

Давайте напишем наш собственный sequence. Что мы хотим сделать?

sequence [] = return []
sequence (m:ms) = do
    x <- m
    xs <- sequence ms
    return $ x:xs
  • Если в списке ничего не осталось, верните (введите в монаду) пустой список результатов.
  • В противном случае, в монаде,
    • Привязать (для монады IO это означает выполнение) первого результата.
    • sequence остальная часть списка; свяжите этот список результатов.
    • Возвращает минус первого результата и список других результатов.

Библиотека GHC использует нечто большее, чем foldr (liftM2 (:)) (return []), но это сложнее объяснить новичкам; на данный момент, просто возьмите мое слово, что они эквивалентны.

sequence_ проще, так как он не заботится о том, чтобы отслеживать результаты. Библиотека GHC реализует ее как sequence_ ms = foldr (>>) (return ()) ms. Допустим только разложение определения foldr:

  sequence [a, b, c, d]
= foldr (>>) (return ()) [a, b, c, d]
= a >> (b >> (c >> (d >> return ())))

Другими словами, "do a, отбросить результат, do b, отбросить результат, & hellip; наконец, вернуть ()".

mapM  f xs = sequence  $ map f xs
mapM_ f xs = sequence_ $ map f xs

С другой стороны, вам даже не нужно знать монады вообще с альтернативным решением unlines.

Что делает unlines? Ну, lines "a\nb\nc\nd\n" = ["a", "b", "c", "d"], поэтому, конечно, unlines ["a", "b", "c", "d"] = "a\nb\nc\nd\n".

unlines $ map fizzBuzz [1..100]= unlines ["1", "2", "Fizz", ..]= "1\n2\nFizz\n...", и он переходит в putStr. Благодаря магии Haskell лень, полная строка никогда не должна быть построена в памяти, так что это будет радостно идти к [1..1000000] или выше:)