2010-01-06 13 views
11

Jestem nowy w haskell i przeczytałem i przetrawiłem Learn You A Haskell For Great Good, wypróbowując kilka rzeczy po drodze. Do mojego pierwszego projektu chciałem wypróbować klasyczny: FizzBuzz. Więc wymyśliłem następujący kod:Haskell - problem z ładnym drukowaniem listy

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] 

Świetnie się spisałam, poza tym, że mam dość gęstą i trudną do odczytania listę. Więc próbowałem to główną funkcję zamiast:

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

I to daje mi błąd Couldn't match expected type 'IO t' against inferred type '[IO()]'. Próbowałem pół tuzina rzeczy i nic z tego nie pomagało. Jaki jest właściwy sposób robienia tego, co próbuję zrobić?

Odpowiedz

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

Masz listę działań IO().

main :: IO() 

Musisz dołączyć je do pojedynczej akcji IO().

Co chcesz zrobić, to wykonać każdy z tych IO() działań w sequence/sequence_:

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

Dla wygody mapM/mapM_ będzie mapować funkcję na liście i kolejności wynikające monadycznego wyników.

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

Więc stały kod będzie wyglądać następująco:

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

Chociaż pewnie bym napisać to tak:

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

lub nawet to:

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

Napiszmy własne sequence. Co mamy zrobić?

sequence [] = return [] 
sequence (m:ms) = do 
    x <- m 
    xs <- sequence ms 
    return $ x:xs 
  • Jeśli nie ma nic na liście, powrót (wstrzyknąć do monady) pusta lista wyników.
  • W przeciwnym razie, w monady,
    • Bind (dla IO monady, oznacza to wykonać) pierwszy wynik.
    • sequence do końca listy; powiązać tę listę wyników.
    • Zwraca wadę pierwszego wyniku i listę innych wyników.

biblioteka GHC wykorzystuje coś bardziej jak foldr (liftM2 (:)) (return []) ale trudniej wyjaśnić przybysza; na razie uwierz mi, że są one równoważne.

sequence_ jest łatwiejsze, ponieważ nie przeszkadza w śledzeniu wyników. Biblioteka GHC implementuje ją jako sequence_ ms = foldr (>>) (return()) ms. Miejmy tylko poszerzyć definicję foldr:

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

Innymi słowy, "nie a, odrzucić wynik; zrobić b; odrzucić wynik, & hellip; wreszcie powrócić ()".

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

Z drugiej strony, nie trzeba nawet znać monady w ogóle z alternatywnym unlines rozwiązania.

Co robi unlines? Cóż, lines "a\nb\nc\nd\n" = ["a", "b", "c", "d"], więc oczywiście unlines ["a", "b", "c", "d"] = "a\nb\nc\nd\n".

unlines $ map fizzBuzz [1..100] = unlines ["1", "2", "Fizz", ..] = "1\n2\nFizz\n..." i poza nim przechodzi do putStr. Dzięki magii lenistwa Haskella, cały ciąg nigdy nie musi być zbudowany w pamięci, więc to szczęśliwie pójdzie do [1..1000000] lub wyżej :)

+0

Dziękuję, wiedziałem, że haskell miał coś takiego, ale nie mogłem tego zrozumieć na zewnątrz! teraz, aby zrozumieć * dlaczego * działa ... :) – RCIX

+0

Jak próbowałem wyjaśnić, wygenerowałeś listę akcji 'IO()', ale musisz powiedzieć * co * chcesz z nimi zrobić. 'sequence' uruchamia listę monad w kolejności, która jest tym, czego chcesz tutaj. Moje ostatnie rozwiązanie zamiast tego łączy ciągi fizz/buzz w jeden ciąg wielowierszowy, zanim wyruszy w dziwną krainę "IO". – ephemient

+0

Tak, po prostu nie nawiązywałem połączenia między monadą a operacją IO - niektóre z tutoriali są wciąż trochę rozmyte – RCIX

Powiązane problemy