Ввод / вывод
Задачи
монады
монады
Все монады представлены в лице класса типов Monad - IO
Компоновка >>=
пример
Затем >>
return
Функции вывода
Исполняемый файл
Функции ввода
getLine
getLine
программа считывает и суммирует два числа типа Integer
getChar Программа принимает три символа с клавиатуры и выводит их в обратном порядке:
Файлы
readFile
writeFile
Добавление данных в файл appendFile
openFile
  
Аргументы командной строки getArgs :: IO [String]
Основные подходы
Простой ввод-вывод
Список действий
Копирование файлов
View , Add , Remove
View , Add , Remove
View , Add , Remove
 Обработка исключений в монаде ввода - вывода
Ошибки ввода - вывода
Ошибки ввода - вывода
Обработка ошибок
Дополнительная литература

Вывод строки символов на экран монитора. Ввод строки символов с клавиатуры. Создание и удаление файлов

1. Ввод / вывод

2. Задачи

• Вывод строки символов на экран монитора
• Ввод строки символов с клавиатуры
• Создание и удаление файлов
• Чтение данных из файла
• Запись данных в файл
• Работа с командной строкой

3. монады

• Монада ввода - вывода используется в Haskell как связующее
звено между значениями, присущими функциональному
языку, и действиями, характеризующими операции ввода вывода и императивное программирование в общем.
• Порядок вычисления выражений в Haskell ограничен только
зависимостями данных; реализация обладает значительной
свободой в выборе этого порядка. Действия, тем не менее,
должны быть упорядочены определенным образом для
выполнения программы и, в частности, ввода - вывода, для
того чтобы быть правильно интерпретированы.
• В Haskell монада ввода - вывода предоставляет пользователю
способ указать последовательное связывание действий, и
реализация обязана соблюдать этот порядок.

4. монады

• Термин монада происходит из отрасли
математики, известной как теория категорий.
Однако, с точки зрения программиста Haskell ,
лучше думать о монаде как об абстрактном
типе данных. В случае монады ввода - вывода
абстрактными значениями являются
упомянутые выше действия. Некоторые
операции являются примитивными действиями,
соответствующими обычным операциям ввода вывода.
Специальные операции последовательно
связывают действия, соответствующие
последовательным операторам (таким как точка
с запятой) в императивных языках.

5. Все монады представлены в лице класса типов Monad - IO

class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a

6. Компоновка >>=

Компоновка >>=
• Метод (>>=) - это оператор последовательной
компоновки (sequentially composition). Иногда его
ещё называют оператором связывания (bind):
он связывает два IO-действия воедино, извлекая
результат, возвращённый левым действием, и
передавая его в качестве аргумента правому
действию
• (>>=) :: IO a -> (a -> IO b) -> IO b
• Первый аргумент - это IO-действие, которое,
выполнив свою работу, вернёт значение некоторого
типа a. Второй аргумент - это функция,
принимающая значение типа a в качестве аргумента
и возвращающая IO-действие, которое, выполнив
свою работу, вернёт значение некоторого типа b.

7. пример

main :: IO ()
main = do
text <- getLine
putStrLn $ "You said ‘ " ++ text ++ " ‘ "
main :: IO ()
main = getLine >>= \text -> putStrLn $ "You said ‘ " ++ text ++ " ‘ "
Когда “запускается” функция getLine, возвращающая IO-монаду, содержащую
полученную от пользователя строку, оператор >>= вытаскивает эту строку из
монады и сразу же передаёт её в качестве аргумента λ-функции, “запускающей”
функцию putStrLn, которая в свою очередь вернёт другую IO-монаду.

8. Затем >>

Затем >>
• Монадический оператор >> - это оператор
“затем” (then). Это простейший случай
связывания: действия связываются без
извлечения значений.
main :: IO ()
main = do
putStrLn "Denis"
putStrLn "Shevchenko"
main :: IO ()
main = putStrLn "Denis" >> putStrLn "Shevchenko"

9. return

obtainTextFromUser :: IO String
obtainTextFromUser = do
putStrLn "Enter your text, please: "
firstText <- getLine
return $ "'" ++ firstText ++ "'
Функция getLine вернёт нам монаду, из которой оператор
компоновки вытащит введённую пользователем строку. Эта
строка поступит на вход λ-функции, которая в свою очередь
создаст новую строку на основе строки, введённой
пользователем, после чего - !!! - функция return вернёт эту
новоиспечённую строку обратно в IO-монаду. Вытащили
значение из монады, что-то с ним сделали, а потом вернули в
монаду.

10.

Пример IO в do нотации:
main = do
putStrLn "Hello, what is your name?"
name <- getLine
putStrLn ("Hello " ++ name ++ "!")
или, в терминах bind, использование специальной формы:
main = putStrLn "Hello, what is your name?" >>
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")
или, очень примитивно, без специальной формы для bind:
main = putStrLn "Hello, what is your name?" >>= \x ->
getLine >>= \name ->
putStrLn ("Hello " ++ name ++ "!")

11. Функции вывода

putChar :: Char -> IO ()
putStr :: String -> IO ()
putStrLn :: String -> IO () --добавляет символ новой строки
print :: Show a => a -> IO ()
Prelude> putStr ”Hello” >> putChar ’ ’ >> putStrLn ”World!”
Hello World!
Функция print выводит значение любого пригодного для печати типа на стандартное
устройство вывода.
Пригодные для печати типы --- это те типы, которые являются экземплярами класса
Show;
print преобразует значения в строки для вывода, используя операцию show, и
добавляет символ новой строки.
main = print ([(n, 2^n) | n <- [0..19]])
программа для печати первых 20 целых
чисел и их степеней 2

12. Исполняемый файл

main = putStrLn "hello, world"
Сохраняем в файле
Helloword.hs и компилируем
В текущей директории
ghc --make helloworld.hs
Появились объектный и
интерфейсный файлы
Запускаем Exe

13. Функции ввода

getChar :: IO Char
-- читаем символ
getLine :: IO String -- читаем строку
getContents :: IO String -- Считывание всего ввода
interact :: (String -> String) -> IO ()
readIO
:: Read a => String -> IO a
readLn
:: Read a => IO a
Следующая программа просто удаляет все символы, не являющиеся ASCII,
из своего стандартного ввода и отображает результат на своем
стандартном выводе. (Функция isAscii определена в Data.Char )
main = interact (filter isAscii)

14. getLine

main = do
putStrLn "Hello, what's your name?"
name <- getLine
putStrLn ("Hello" ++ name ++ ": ) ! ")

15. getLine

import Data.Char
main = do
putStrLn "What's your first name?"
firstName <- getLine
putStrLn "What's your last name?"
lastName <- getLine
let bigFirstName = map toUpper firstName
bigLastName = map toUpper lastName
putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?"

16. программа считывает и суммирует два числа типа Integer

import System.IO
main = do
hSetBuffering stdout NoBuffering
putStr "Введите новое целое число: "
x1 <- readNum
putStr "Введите другое целое число: "
x2 <- readNum
putStr ("Их сумма равна " ++ show (x1+x2) ++ "\n")
where readNum :: IO Integer
-- Указание сигнатуры типа позволяет избежать
-- исправления типов x1,x2 правилом по умолчанию
readNum = readLn
Summ.hs

17. getChar Программа принимает три символа с клавиатуры и выводит их в обратном порядке:

import Control.Applicative
f :: Char -> Char -> Char -> String
f a b c = reverse $ [a,b,c]
main :: IO ()
main = print =<< f <$> getChar <*> getChar <*> getChar
Сохраним в файле ReverseIO.hs и скомпилируем:
ghc --make ReverseIO -o rev3
Дополнительным флагом -o мы попросили компилятор чтобы он сохранил
исполняемый файл под именем rev3.
./ rev3
Запуск в командной строке

18. Файлы

type FilePath = String
writeFile :: FilePath -> String -> IO () -- запись строки в файл
добавление строки
appendFile :: FilePath -> String -> IO () --в конец
файла
readFile :: FilePath -> IO String
-- чтение файла
Обратите внимание, что writeFile и appendFile записывают литеральную строку в
файл. Для того чтобы записать значение любого пригодного для печати типа, как в
print, сначала используется функция show для преобразования значения в строку.
main = appendFile "квадраты" (show [(x,x*x) | x <- [0,0.1..2]])

19. readFile

readFile
readFile :: FilePath -> IO String
import System.IO
main = do
contents <- readFile “test.txt"
putStr contents

20. writeFile

writeFile :: FilePath -> String -> IO ()
import System.IO
import Data.Char
main = do
contents <- readFile “file.in"
writeFile “file.out" (map toUpper contents)

21. Добавление данных в файл appendFile

import System.IO
main = do
todoItem <- getLine
appendFile "todo.txt" (todoItem ++ "\n")

22. openFile

import System.IO
import System.IO
main = do
handle <- openFile "test.txt" ReadMode
contents <- hGetContents handle
putStr contents
main = do
contents <- readFile «test.txt"
putStr contents
hClose handle

23.   

data IOMode = ReadMode | WriteMode | AppendMode | Read
WriteMode
withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a.
import System.IO
main = do
withFile “test.txt" ReadMode (\handle -> do
contents <- hGetContents handle
putStr contents)

24.

withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
handle <- openFile path mode
result <- f handle
hClose handle
return result
main = do
withFile "something.txt" ReadMode (\handle -> do
hSetBuffering handle $ BlockBuffering (Just 2048)
contents <- hGetContents handle
putStr contents)

25. Аргументы командной строки getArgs :: IO [String]

import System.Environment
import Data.List
main = do
args <- getArgs
progName <- getProgName
putStrLn "The arguments are:"
mapM putStrLn args
putStrLn "The program name is:"
putStrLn progName
$ ./arg-test first second w00t "multi word arg"
The arguments are:
first
second
w00t
multi word arg
The program name is:
arg-test
File3.hs

26. Основные подходы

27. Простой ввод-вывод

Самая простая полезная форма ввода-вывода: прочитать файл, что-то сделать с его
содержимым, а потом записать результаты в файл.
import System.IO
import Data.Char
main :: IO ()
main = do
contents <- readFile "file.in"
writeFile "file.out" (operate contents)
operate :: String -> String
operate = ... -- ваша функция
operate :: String -> String
operate = map toUpper
Эта программа
читает file.in,
выполняет функцию
operate на его
содержимом, а
затем записывает
результат в file.out.
Функция main
содержит все
операции вводавывода, а функция
operate — чистая.

28. Список действий

• Если шаблон, описанный в предыдущем
разделе, недостаточен для ваших задач, то
следующим шагом будет использование списка
действий. Функцию main можно написать так:
main :: IO ()
main = do
x1 <- expr1
x2 <- expr2 ...
xN <- exprN
return ()

29. Копирование файлов

import System.IO
import Data.Char ( toUpper )
import System.Environment
main = do
[f1,f2] <- getArgs
h1 <- openFile f1 ReadMode
import System.IO
import Data.Char ( toUpper )
import System.Environment
main = do
[f1,f2] <- getArgs
s <- readFile f1
writeFile f2 (map toUpper s)
h2 <- openFile f2 WriteMode
copyFile h1 h2
hClose h1
hClose h2
copyFile h1 h2 = do
eof <- hIsEOF h1
if eof then return () else
do
c <- hGetChar h1
hPutChar h2 (toUpper c)
copyFile h1 h2
Копирование
файлов

30.

import System.IO
import System.Directory
import Data.List
Программа удаляет из
файла todo.txt указанную
строку
"todo.txt"
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
main = do
handle <- openFile "todo.txt" ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
putStrLn "These are your TO-DO items:"
putStr $ unlines numberedTasks
putStrLn "Which one do you want to delete?"
numberString <- getLine
let number = read numberString
newTodoItems = delete (todoTasks !! number) todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
These are your TO-DO items:
hClose tempHandle
0 - Iron the dishes
1 - Dust the dog
removeFile "todo.txt"
2 - Take salad out of the oven
renameFile tempName "todo.txt"
Which one do you want to delete?
1
deletetodo.hs

31. View , Add , Remove

import System.Environment
import System.Directory
import System.IO
import Data.List
dispatch :: [(String, [String] -> IO ())]
dispatch = [ ("add", add)
, ("view", view)
, ("remove", remove)
]
main = do (command:args) < getArgs
let (Just action) = lookup command dispatch
action args

32. View , Add , Remove

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
view :: [String] -> IO ()
view [fileName] = do
contents <- readFile fileName
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
putStr $ unlines numberedTasks

33. View , Add , Remove

remove :: [String] -> IO ()
remove [fileName, numberString] = do
handle <- openFile fileName ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let number = read numberString
todoTasks = lines contents
newTodoItems = delete (todoTasks !! number) todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile fileName
renameFile tempName fileName

34.

import System.Environment
import System.Directory
import System.IO
import Data.List
dispatch :: [(String, [String] -> IO ())]
dispatch = [ ("add", add)
, ("view", view)
, ("remove", remove)
]
main = do
(command:args) <- getArgs
let (Just action) = lookup command dispatch
action args
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (tod
oItem ++ "\n")
view :: [String] -> IO ()
view [fileName] = do
contents <- readFile fileName
let todoTasks = lines contents
numberedTasks = zipWith (\n line ->
show n ++ " - " ++ line) [0..] todoTasks
putStr $ unlines numberedTasks
remove :: [String] -> IO ()
remove [fileName, numberString] = do
handle <- openFile fileName ReadMode
(tempName, tempHandle) <openTempFile "." "temp"
contents <- hGetContents handle
let number = read numberString
todoTasks = lines contents
newTodoItems = delete (todoTasks !! number)
todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile fileName
renameFile tempName fileName
Excase.hs
Todo.txt

35.

$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
$ ./todo add todo.txt "Pick up children
from drycleaners"
$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
3 - Pick up children from drycleaners
$ ./todo remove todo.txt 2
$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Pick up children from drycleaners
Excase.hs
Todo.txt

36.  Обработка исключений в монаде ввода - вывода

Обработка исключений в монаде
ввода - вывода
Монада ввода - вывода включает простую систему обработки исключений. Любая операция ввода - вывода может
вызвать исключение вместо возвращения результата.
Исключения в монаде ввода - вывода представлены значениями типа IOError. Это абстрактный тип: его конструкторы
скрыты от пользователя. Библиотека IO определяет функции, которые конструируют и изучают значения IOError .
Единственной функцией Prelude, которая создает значение IOError, является userError. Пользовательские значения
ошибок включают строку с описанием ошибки.
userError :: String -> IOError
Исключения вызываются и отлавливаются с помощью следующих функций:
ioError :: IOError -> IO a
catch :: IO a -> (IOError -> IO a) -> IO a
Функция ioError вызывает исключение; функция catch устанавливает обработчик, который получает любое исключение,
вызванное действием, защищенным catch. Исключение отлавливается самым последним обработчиком, установленным
catch. Эти обработчики не действуют выборочно: они отлавливают все исключения. Распространение исключения нужно
явно обеспечить в обработчике путем повторного вызова любого нежелательного исключения. Например, в
f = catch g (\e -> if IO.isEOFError e then return [] else ioError e)
функция f возвращает [], когда в g возникает исключение конца файла, иначе исключение передается следующему
внешнему обработчику. Функция isEOFError является частью библиотеки IO.
Когда исключение передается за пределы главной программы, система Haskell выводит связанное с ним значение
IOError и выходит из программы.
Метод fail экземпляра IO класса Monad вызывает userError так:
instance Monad IO where
...bindings for return, (>>=), (>>)
fail s = ioError (userError s)

37. Ошибки ввода - вывода

Ошибки типа IOError используются монадой ввода - вывода. Это абстрактный тип; библиотека
обеспечивает функции для опроса и конструирования значений в IOError:
isAlreadyExistsError --- операция завершилась неуспешно, потому что один из ее аргументов уже
существует.
isDoesNotExistError --- операция завершилась неуспешно, потому что один из ее аргументов не
существует.
isAlreadyInUseError --- операция завершилась неуспешно, потому что один из ее аргументов
является однопользовательским ресурсом, который уже используется (например, открытие
одного и того же файла дважды для записи может вызвать эту ошибку).
isFullError --- операция завершилась неуспешно, потому что устройство заполнено.
isEOFError --- операция завершилась неуспешно, потому что был достигнут конец файла.
isIllegalOperation --- операция невозможна.
isPermissionError --- операция завершилась неуспешно, потому что пользователь не имеет
достаточно привилегий операционной системы на выполнение этой операции.
isUserError --- определенное программистом значение ошибки вызвано использованием fail.

38. Ошибки ввода - вывода

Все эти функции возвращают значение типа Bool, которое равно True, если ее
аргументом является соответствующий вид ошибки, и False иначе.
Любая функция, которая возвращает результат IO, может завершиться с
ошибкой isIllegalOperation. Дополнительные ошибки, которые могут быть
вызваны реализацией, перечислены после соответствующей операции. В
некоторых случаях реализация не способна различить возможные причины
ошибки. В этом случае она должна вернуть isIllegalOperation.
Имеются три дополнительные функции для того, чтобы получить информацию
о значении ошибки, --- это ioeGetHandle, которая возвращает Just hdl, если
значение ошибки относится к дескриптору hdl, и Nothing иначе;
ioeGetFileName, которая возвращает Just имя, если значение ошибки
относится к файлу имя, и Nothing иначе; и ioeGetErrorString, которая
возвращает строку. Для "пользовательских" ошибок (которые вызваны
использованием fail), строка, возвращенная ioeGetErrorString, является
аргументом, который был передан в fail; для всех остальных ошибок строка
зависит от реализации.
Функция try возвращает ошибку в вычислении, явно использующем тип Either .
Функция bracket охватывает обычный способ выделения памяти, вычисления
и освобождения памяти, в котором шаг освобождения должен произойти
даже в случае ошибки во время вычисления. Это аналогично try-catch-finally в
Java.

39. Обработка ошибок

doesFileExist
doesFileExist :: FilePath -> IO Bool
import System.Environment
import System.IO
import System.Directory
main = do (fileName:_) <- getArgs
fileExists <- doesFileExist fileName
if fileExists
then do contents <- readFile fileName
putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
else do putStrLn "The file doesn't exist!"
Error.hs

40.

import System.Random
import Control.Monad(when)
Генерация случайных чисел
main = do
gen <- getStdGen
askForNumber gen
askForNumber :: StdGen -> IO ()
askForNumber gen = do
let (randNumber, newGen) = randomR (1,10) gen :: (Int, StdGen)
putStr "Which number in the range from 1 to 10 am I thinking of? "
numberString <- getLine
when (not $ null numberString) $ do
let number = read numberString
if randNumber == number
then putStrLn "You are correct!"
else putStrLn $ "Sorry, it was " ++ show randNumber
askForNumber newGen
Rnd.hs

41. Дополнительная литература

• http://eax.me/monads/ О монадах
• http://habrahabr.ru/post/80396/ Объяснение вводавывода в Haskell без монад
• http://ru-lambda.livejournal.com/12467.html
• http://channel9.msdn.com/Series/C9-Lectures-ErikMeijer-Functional-Programming-Fundamentals/C9Lectures-Dr-Erik-Meijer-Functional-ProgrammingFundamentals-Chapter-7-of-13
• http://haskell.ru/io.html
• https://wiki.haskell.org/Ru/IO_Inside Система
ввода/вывода
English     Русский Правила