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 :)
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
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
Tak, po prostu nie nawiązywałem połączenia między monadą a operacją IO - niektóre z tutoriali są wciąż trochę rozmyte – RCIX