Haskell - проблема с красивой печатью списка - PullRequest
11 голосов
/ 07 января 2010

Я новичок в Haskell, и я прочитал и переварил Learn You 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 Ответ

26 голосов
/ 07 января 2010
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
  • Если в списке ничего не осталось, вернуть (ввести в монаду) пустой список результатов.
  • В противном случае, внутри монады,
    • Bind (для монады 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. Благодаря магии лени Хаскелла, полная строка никогда не должна создаваться в памяти, поэтому она с радостью перейдет к [1..1000000] или выше:)

...