Душкин Роман Викторович darkus@yandex ru Москва, 2001 лекция

Вид материалаЛекция

Содержание


Программирование при помощи действий
Обработка исключений
Файлы, каналы и обработчики
Подобный материал:
1   ...   11   12   13   14   15   16   17   18   19

Программирование при помощи действий


Действия ввода/вывода являются обычными значениями в терминах 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_ для выполнения.

Обработка исключений


Что делать, если в процессе операций ввода/вывода возникла неординарная ситуация? Нап­ример, функция getChar обнаружила конец файла. В этом случае произойдет ошибка. Как и любой продвинутый язык программирования Haskell предлагает для этих целей ме­ха­низм обработки исключений. Для этого не используется какой-то специальный син­так­сис, но есть специальный тип IOError, который содержит описания всех возникаемых в про­цессе ввода/вывода ошибок.

Обработчик исключений имеет тип (IOError  IO a), при этом функция catch ас­со­ци­и­ру­ет (свя­зывает) обработчик исключений с набором действий:

catch :: IO a -> (IOError -> IO a) -> IO a

Аргументами этой функции являются действие (первый аргумент) и обработчик ис­клю­че­ний (второй аргумент). Если действие выполнено успешно, то просто возвращается ре­зуль­тат без возбуждения обработчика исключений. Если же в процессе выполнения дей­с­т­вия возникла ошибка, то она передается обработчику исключений в качестве операнда ти­па IOError, после чего выполняется сам обработчик.

Таким образом, можно написать более сложные функции, которые будут грамотно вес­ти себя в случае выпадения ошибочных ситуаций:

getChar’ :: IO Char

getChar’ = getChar `catch` eofHandler

where eofHandler e = if isEofError e then return \’n\ else ioError e


getLine’ :: IO String

getLine’ = catch getLine’’ (\err -> return (”Error: ” ++ show err))

where getLine’’ = do c <- getChar’

id c == ’\n’ then return ””

else do l <- getLine’

return (c : l)

В этой программе видно, что можно использовать вложенные друг в друга обработчики ошибок. В функции getChar’ отлавливается ошибка, которая возникает при обнаружении сим­вола конца файла. Если ошибка другая, то при помощи функции ioError она от­прав­ля­ет­ся дальше и ловится обработчиком, который «сидит» в функции getLine’. Для оп­ре­де­лён­ности в Haskell’е предусмотрен обработчик исключений по умолчанию, который на­хо­дит­ся на самом верхнем уровне вложенности. Если ошибка не поймана ни одним об­ра­бот­чи­ком, который написан в программе, то её ловит обработчик по умолчанию, который вы­во­дит на экран сообщение об ошибке и останавливает программу.

Файлы, каналы и обработчики


Для работы с файлами Haskell предоставляет все возможности, что и другие языки программирования. Однако большинство этих возможностей определены в модуле IO, а не в Prelude, поэтому для работы с файлами необходимо явно импортировать модуль IO.

Открытие файла порождает обработчик (он имеет тип Handle). Закрытие обработчика ини­циирует закрытие соответствующего файла. Обработчики могут быть также ас­со­ци­и­ро­ваны с каналами, т.е. портами взаимодействия, которые не связаны напрямую с фай­лами. В Haskell’е предопределены три таких канала — stdin (стандартный канал ввода), stdout (стандартный канал вывода) и stderr (стандартный канал вывода сообщений об ошиб­ках).

Таким образом, для использования файлов можно пользоваться следующими вещами:

type FilePath = String

openFile :: FilePath -> IOMode -> IO Handle

hClose :: Handle -> IO ()

data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode

Далее приводится пример программы, которая копирует один файл в другой:

main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode
          toHandle   <- getAndOpenFile "Copy to: " WriteMode 
          contents   <- hGetContents fromHandle
          hPutStr toHandle contents
          hClose toHandle

hClose fromHandle
          putStr "Done."

getAndOpenFile           :: String -> IOMode -> IO Handle
getAndOpenFile prompt mode =
    do putStr prompt
       name <- getLine
       catch (openFile name mode)
             (\_ -> do putStrLn ("Cannot open "++ name ++ "\n")
                       getAndOpenFile prompt mode)

Здесь использована одна интересная и важная функция — hGetContents, которая берёт со­держимое переданного ей в качестве аргумента файла и возвращает его в качестве од­ной длинной строки.