-
Notifications
You must be signed in to change notification settings - Fork 9
/
lecture11_io.tex
244 lines (227 loc) · 11.2 KB
/
lecture11_io.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
% !TeX document-id = {40e7f911-37dd-4856-a048-b5b803f1756a}
% !TeX TXS-program:compile = txs:///pdflatex/[-shell-escape]
\documentclass[11pt]{beamer}
\input{lecture_preamble.tex}
\title{Лекция 11: монада IO и взаимодействие с внешним миром}
\begin{document}
\begin{frame}[plain]
\maketitle
\end{frame}
\begin{frame}[fragile]
\frametitle{Монада IO}
\begin{itemize}
\item Среди многих монад стандартной библиотеки, \haskinline|IO| играет особую роль.
\item Значение типа \haskinline|IO a| описывает вычисление с результатом типа \haskinline|a| и побочными эффектами.
\item В первую очередь имеются в виду ввод-вывод, как видно из названия, но также изменяемые переменные и т.д.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Программы командной строки}
\begin{itemize}
\item Исполняемая программа в Haskell задаётся значением
\haskinline|main :: IO a| (почти всегда \haskinline|IO ()|).
\item Это аналог \mintinline{c}|int main(int argc, char** argv)| в C, \mintinline{java}|public static void main(char[] args)| в Java и т.д.
\item Соответственно, есть доступ к аргументам командной строки и возможность указать код выхода:
\item \haskinline|System.Environment.getArgs :: IO [String]|. Там же есть функции для чтения переменных окружения.
\item \haskinline|System.Exit.exitWith :: ExitCode -> IO a|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Интерактивные программы}
\begin{itemize}
\item Из консоли можно читать текст, вводимый пользователем:
\begin{haskell}
getLine ::!\pause! IO String
\end{haskell}
\item И писать в неё:
\begin{haskell}
putStr, putStrLn ::!\pause! String -> IO ()
print :: Show a => a -> IO ()
\end{haskell}
\pause
\item Простой пример: читаем любую строку, выводим её длину и повторяем (а на \haskinline|"quit"| выходим):
\pause
\begin{haskellsmall}
main = do
line <- getLine
if line /= "quit"
then do
print (length line)
main
else
pure () -- или exitSuccess
\end{haskellsmall}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Работа с файлами}
\begin{itemize}
\item Модуль \href{http://hackage.haskell.org/package/base/docs/System-IO.html}{\haskinline|System.IO|}.
\item Напомню, \haskinline|type FilePath = String|.
\item Простейшие операции это чтение и запись в файл:
\begin{haskell}
readFile :: FilePath -> IO String
writeFile, appendFile ::
FilePath -> String -> IO ()
\end{haskell}
\item
Или можно открыть файл один раз, получить \haskinline|Handle| (дескриптор файла) и работать с ним:
\begin{haskell}
openFile, openBinaryFile ::
FilePath -> IOMode -> IO Handle
hPutStrLn :: Handle -> String -> IO ()
hGetStr...
\end{haskell}
\pause
\item
Функции работы с консолью сводятся к этим:
\begin{haskell}
getLine = hGetLine stdin
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Работа с файлами (2)}
\begin{itemize}
\item В чём проблема с кодом вроде
\begin{haskell}
do
file <- openFile path ReadWriteMode
...
hClose file
\end{haskell}
\pause
\item Что, если на каком-то из шагов случится исключение? Мы остаёмся с открытым файлом.
\item Если это случится много раз, программа вылетит.
\pause
\item Чтобы закрыть файл и при исключении:
\begin{haskell}
withFile path ReadWriteMode $ \file -> do
...
\end{haskell}
\item Это частный случай функции \haskinline|bracket|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Ленивый ввод-вывод}
\begin{itemize}
\item Функция \haskinline|hGetContents| не читает содержимое файла, а сразу возвращает строку, по мере доступа к которой читается файл.
\item С одной стороны, это хорошо: не нужно явно указывать, сколько читать.
\item Но если закрыть файл до того, как он реально прочитан, строка закончится как если бы она дошла до конца файла:
\begin{haskellsmall}
wrong = do
fileData <- withFile "test.txt" ReadMode hGetContents
putStr fileData
\end{haskellsmall}
\item Нужно использовать данные внутри \haskinline|withFile|:
\begin{haskellsmall}
right = withFile "test.txt" ReadMode $ \file -> do
fileData <- hGetContents file
putStr fileData
\end{haskellsmall}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{\haskinline|ByteString| и \haskinline|Text|}
\begin{itemize}
\item Мы знаем, что \haskinline|String| занимает очень много места в памяти, что ухудшает и время работы.
\item Вместо него есть \haskinline|Data.ByteString.ByteString|, по сути представляющий собой массив байтов, и \haskinline|Data.ByteString.Lazy.ByteString| как ленивый список таких массивов.
\item Многие функции для работы с ними называются так же, как для \haskinline|String|, но живут в соответствующих модулях (поэтому используется \haskinline|import qualified|).
\item Они годятся для бинарных данных (или ASCII, с помощью \haskinline|Data.ByteString[.Lazy].Char8|).
\item Для текста аналогичный пакет \haskinline|text| и типы в модулях \haskinline|Data.Text[.Lazy]|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Случайные значения}
\begin{itemize}
\item Модуль \haskinline|System.Random| содержит класс
\item
\begin{haskellsmall}
class RandomGen g where
next :: g -> (Int, g)
split :: g -> (g, g)
genRange :: g -> (Int, Int)
genRange _ = (minBound, maxBound)
\end{haskellsmall}
описывающий генераторы случайных \haskinline|Int|.
\item Есть тип \haskinline|StdGen| и \haskinline|instance RandomGen StdGen|.
\item Глобальный генератор живёт в \haskinline|IORef|:
\begin{haskellsmall}
getStdGen :: IO StdGen
setStdGen :: StdGen -> IO ()
newStdGen :: IO StdGen -- применяет split к getStdGen
getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
\end{haskellsmall}
\item Типы \haskinline|next| и \haskinline|getStdRandom| могут напомнить про \haskinline|State| и не зря!
\item Реализуем \haskinline|newStdGen| и \haskinline|getStdRandom|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Случайные значения (ответ)}
\begin{itemize}
\item
\begin{haskell}
newStdGen = do
currGen <- getStdGen
let (newGen1, newGen2) = split currGen
setStdGen newGen2
pure newGen1
\end{haskell}
\pause
\item
\begin{haskell}
getStdRandom f = do
currGen <- getStdGen
(result, newGen) = f currGen
setStdGen newGen
pure result
\end{haskell}
\pause
\item Можем выразить одно через другое?
\pause
\begin{haskell}
newStdGen = getStdRandom split
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Случайные значения (2)}
\begin{itemize}
\item Ещё один класс в \haskinline|System.Random| описывает типы, для которых можно получить случайные значения, если есть ГСЧ:
\item
\begin{haskell}
class Random a where
randomR :: RandomGen g => (a, a) -> g -> (a, g)
random :: RandomGen g => g -> (a, g)
\end{haskell}
Первый аргумент \haskinline|randomR|: диапазон, в котором берутся значения.
\item Опять видим тип, похожий на \haskinline|State|.
\item
\begin{haskell}
randomRIO :: Random a => (a, a) -> IO a
randomIO :: Random a => IO a
\end{haskell}
используют глобальный генератор.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Побег из \haskinline|IO|}
\begin{itemize}
\item Если у нас есть значение типа \haskinline|IO a|, можно ли превратить его просто в \haskinline|a|?
\pause
\item На самом деле есть функция \haskinline|unsafePerformIO :: IO a -> a|.
\item Но само название говорит о её небезопасности.
\item Конкретное использование может быть и безопасным, но для этого надо знать довольно много о внутренностях Haskell.
\pause
\item Поэтому в рамках этого курса мы её использовать не будем, как и другие \haskinline|unsafe*IO|.
\item А есть ещё \haskinline|accursedUnutterablePerformIO|\ldots
\end{itemize}
\end{frame}
% TODO добавить ссылки
% \begin{frame}[fragile]
% \frametitle{Дополнительное чтение}
% \begin{itemize}
% \item TODO
% \end{itemize}
% \end{frame}
\end{document}