Действия ввода/вывода являются обычными значениями в терминах Haskell’а. Т.е. действия можно передавать в функции в качестве параметров, заключать в структуры данных и вообще использовать там, где можно использовать данные языка Haskell. В этом смысле система операций ввода/вывода является полностью функциональной. Например, можно предположить список действий:
todoList :: [IO ()]
todoList = [putChar ’a’,
do putChar ’b’
putChar ’c’,
do c <- getChar
putChar c]
Этот список не возбуждает никаких действий, он просто содержит их описания. Для того чтобы выполнить эту структуру, т.е. возбудить все ее действия, необходима некоторая функция (например, sequence_):
sequence_ :: [IO ()] -> IO ()
sequence_ [] = return ()
sequence_ (a:as) = do a
sequence as
Эта функция может быть полезна для написания функции putStr, которая выводит строку на экран:
putStr :: String -> IO ()
putStr s = sequence_ (map putChar s)
На этом примере видно явное отличие системы операций ввода/вывода языка Haskell от систем императивных языков. Если бы в каком-нибудь императивном языке была бы функция map, она бы выполнила кучу действий. Вместо этого в Haskell’е просто создается список действий (одно для каждого символа строки), который потом обрабатывается функцией sequence_ для выполнения.