-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy path2.6-haskell_io.tex
417 lines (372 loc) · 19.5 KB
/
2.6-haskell_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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
\documentclass[alsotrans,beameroptions={aspectratio=169}]{beamerswitch}
\usepackage{fprog}
% взели ли сме вече лениво оценяване?
\newboolfalse{lazy}
% компилация по части
\newbooltrue{parts}
% взели ли сме вече операцията ($)?
\newbool{app}
\ifbool{parts}{
\newbool{part1}
\newbool{part2}
\IfEndWith*{\JobName}{part1}{\booltrue{part1}}{
\IfEndWith*{\JobName}{part1-trans}{\booltrue{part1}}{
\IfEndWith*{\JobName}{part2}{\booltrue{part2}}{
\IfEndWith*{\JobName}{part2-trans}{\booltrue{part2}}{
\IfEndWith*{\JobName}{-trans}{}{
% в общия случай пускаме компилация на частите
\BeamerswitchSpawn{-part1}
\BeamerswitchSpawn{-part1-trans}
\BeamerswitchSpawn{-part2}
\BeamerswitchSpawn{-part2-trans}}
% приключваме с текущата обработка, частите вече са компилирани
\stop}}}}}{
% включване и на двете части
\newbooltrue{part1}
\newbooltrue{part2}
}
\ifbool{part1}{\boolfalse{app}}{}
\ifbool{part2}{\booltrue{app}}{}
\title{Входно-изходни операции в Haskell\ifbool{parts}{ -- част \ifbool{part1}{1}{}\ifbool{part2}{2}{}}{}}
\date[\ifbool{part1}{29.11--4.12.2024 г.}{18.12.2024 г.}]{\ifbool{part1}{29 ноември--4 декември 2024 г.}{18 декември 2024 г.}}
\lstset{language=Haskell,style=Haskell}
\begin{document}
\begin{frame}
\titlepage
\end{frame}
\section{Странични ефекти в Haskell}
\begin{frame}<\switch{part1}>
\frametitle{Странични ефекти в Haskell}
\begin{itemize}[<+->]
\item Функциите в Haskell нямат странични ефекти
\item Но входно-изходните операции по природа са странични ефекти!
\item Как можем да се справим с този парадокс?
\item \textbf{Идея:} Можем да си мислим за входно-изходните операции като поточна обработка на данни
\end{itemize}
\onslide<+->
\begin{center}
производител
$\longrightarrow$
\begin{tikzpicture}[scale=.7,baseline={(0,0)}]
\filldraw[draw=black,fill=diagramblue]
(0,1) .. controls +(2,1.5) and +(-2,-1.5) .. (8,1) --
(8,-1) .. controls +(-2,-1.5) and +(2,1.5) .. (0,-1) -- (0,1);
\path (0,0) .. controls +(2,1.5) and +(-2,-1.5) .. (8,0)
\foreach \p in {.1,.2,...,.9} {
node[circle,inner sep=0,minimum size=6,draw=black,fill=yellow!40!white,pos=\p] {}
};
\end{tikzpicture} $\longrightarrow$ консуматор
\end{center}
\end{frame}
\begin{frame}<\switch{part1}>
\frametitle{Поточна обработка}
\textbf{Задача.} Да се въведат \tt{n} числа и да се изведе тяхното средно аритметично.\\
\pause
\textbf{Решение:} Дефинираме трансформация над стандартните вход и изход, която:
\begin{itemize}[<+->]
\item приема \tt{n} като параметър
\item трансформира входния поток, като \alert{консумира} от него \tt{n} числа и връща списък, който ги съдържа
\item пресмята средното аритметично \tt{avg} на числата в списъка
\item трансформира изходния поток, като \alert{произвежда} върху него низовото представяне на числото \tt{avg}
\end{itemize}
\onslide<+->
\textbf{Трансформирането} на входно-изходните потоци несъмнено е страничен ефект, но \textbf{конструирането на трансформацията} няма нужда от странични ефекти!\\
\onslide<+->
\alert{Функциите, които работят с вход и изход, по същество дефинират композиция на входно-изходни трансформации.}
\end{frame}
\section{\tt{IO}}
\begin{frame}<\switch{part1}>
\frametitle{Типът \tt{IO a}}
Стандартният генеричен тип \lst{IO a} задава тип на входно/изходна трансформация, резултатът от която е от тип \tt{a}.\\
\pause
\textbf{Частен случай:} \lst{IO ()} задава трансформация, която връща празен резултат.\\[2ex]
\pause
\textbf{Входни трансформации:}
\begin{itemize}
\item \lst{getChar :: IO Char} --- връща символ, прочетен от входа
\item \lst{getLine :: IO String} --- връща ред, прочетен от входа
\end{itemize}
\pause
\textbf{Изходни трансформации:}
\begin{itemize}
\item \lst{putChar :: Char -> IO ()} --- извежда символ на изхода
\item \lst{putStr :: String -> IO ()} --- извежда низ на изхода
\item \lst{putStrLn :: String -> IO ()} --- извежда ред на изхода
\end{itemize}
\end{frame}
\begin{frame}<\switch{part1}>[fragile]
\frametitle{Главна функция \tt{main}}
\begin{itemize}[<+->]
\item Функцията \lst{main :: IO ()} от модула \lst{Main} в Haskell е
специална: тя е входната точка на компилираната програма.
\item По същество тя дефинира входно-изходна трансформация, която се прилага към стандартния вход и изход при изпълнение на програмата.
\item \textbf{Пример:} \lst^main = putStrLn "Hello, world!"^
\item Можем ли да дефинираме \lst!main = putStrLn !\ifbool{app}{\lst!\$! }{\lst!(!}\lst!"Въведохте: " ++ getLine!\ifbool{app}{}{\lst!)!}? % поправка на оцветяването на синтаксис$
\item \alert{Не!} \hspace{3em} \lst{getLine :: }\tta{IO} \lst{String}
\item Композицията на входно-изходни трансформации работи по различен начин от композицията на функции
\item Низът, който връща \lst{getLine} е „замърсен“ от входно-изходна операция
\item Как да композираме трансформации?
\end{itemize}
\end{frame}
\section{Синтаксис за вход/изход}
\begin{frame}<\switch{part1}>[fragile]
\frametitle{Конструкцията \lst{do}}
В Haskell има специален \alert{двумерен} синтаксис за композиране на трансформации:\\[1ex]
\tta{do} \{ <трансформация> \}\\[1ex]
\pause
<трансформация> може да бъде:
\begin{itemize}[<+->]
\item произволен израз от тип \lst{IO a}
\item{} <име> \tta{<-} <трансформация>
\begin{itemize}
\item{} <трансформация> е от тип \lst{IO a}
\item резултатът от <трансформация> се свързва с <име>
\end{itemize}
\item \tta{return} <израз>
\begin{itemize}
\item празна трансформация, която връща <израз> като резултат
\item \lst{return :: a -> IO a}
\end{itemize}
\item резултатът от цялата конструкция \tt{do} е резултатът от последната трансформация в композицията
\end{itemize}
\onslide<+->
\vspace{-.5ex}
\begin{lstlisting}
main = do line <- getLine
putStrLn @\ifbool{app}{\$ }{(}@"Въведохте: " ++ line@\ifbool{app}{}{)}@
\end{lstlisting}
\end{frame}
\begin{frame}<\switch{part1}>[fragile]
\frametitle{Локални дефиниции в \lst{do}}
\small
В някакъв смисъл \lst{<-} и \lst{return} са обратни една на друга операции:
\begin{itemize}[<+->]
\item \lst{<-} извлича „чист“ резултат от тип \tt{a} от трансформация от тип \lst{IO a}
\item \lst{return} фиктивно „замърсява“ резултат от тип \tt{a} за да стане от тип \lst{IO a}
\item Какъв е ефектът от <име> \lst{<- return} <израз> в \lst{do} конструкция?
\item Създава се локалната дефиниция <име> = <израз>!
\item Алтернативно, локални дефиниции могат да се създават и чрез: \lst{let} <име> \tta= <израз>
\item Да не се бърка с \lst{let} <име> \tt= <израз> \tta{in} <израз>!
\end{itemize}
\onslide<+->
\textbf{Пример:}
\vspace{-.5ex}
\lstfootnotesize
\begin{lstlisting}
main = do putStrLn "Моля, въведете палиндром: "
line <- getLine
let revLine = reverse line
if revLine == line then putStrLn "Благодаря!"
else do putStrLn @\ifbool{app}{\$ }{(}@line ++ " не е палиндром!"@\ifbool{app}{}{)}@
main
\end{lstlisting}
\end{frame}
\begin{frame}<\switch{part1}>
\frametitle{Вход и изход на данни}
Как можем да извеждаме и въвеждаме данни от типове различни от \lst{Char} и \tt{String}?\\[2ex]
\pause
На помощ идват класовете \lst{Show} и \lst{Read}:
\begin{itemize}[<+->]
\item \lst{show :: Show a => a -> String}
\item \lst{print :: Show a => a -> IO ()}
\item \lst{print = putStrLn . show}
\item \lst{read :: Read a => String -> a}
\item \evalstoerrp{read "1.23"}
\item Haskell не може да познае типа на резултата, понеже е генеричен!
\item \lst{getInt :: IO Int}
\item \lst{getInt = do line <- getLine}\\
\hspace{7em}\lst!return! \ifbool{app}{\lst! \$! }{ \lst!(!}\lst!read line!\ifbool{app}{}{\lst!)!}
\end{itemize}
\end{frame}
\begin{frame}<\switch{part1}>[fragile]
\frametitle{Пример: средно аритметично на редица от числа}
\lstsmall
\begin{lstlisting}
findAverage :: IO Double@\pause@
findAverage = do putStr "Моля, въведете брой: "
n <- getInt
s <- readAndSum n
return @\ifbool{app}{\$ }{(}@fromIntegral s / fromIntegral n@\ifbool{app}{}{)}@
@\pause@
readAndSum :: Int -> IO Int@\pause@
readAndSum 0 = return 0
readAndSum n = do putStr "Моля, въведете число: "
x <- getInt
s <- readAndSum @\ifbool{app}{\$ }{(}@n - 1@\ifbool{app}{}{)}@
return @\ifbool{app}{\$ }{(}@x + s@\ifbool{app}{}{)}@
@\pause@
main = do avg <- findAverage
putStrLn @\ifbool{app}{\$ }{(}@"Средното аритметично е: " ++ show avg@\ifbool{app}{}{)}@
\end{lstlisting}
\end{frame}
\section{Управляващи структури}
\begin{frame}<\switch{part2}>
\frametitle{Управляващи функции}
Можем да работим с трансформации с функции от по-висок ред:
\begin{itemize}[<+->]
\item \lst{import Control.Monad}
\item \lst{sequence :: [IO a] -> IO [a]}
\begin{itemize}
\item композира трансформации и събира резултатите им в списък
\item \alt<+->{\lst!getInts = sequence . (`replicate` getInt)!}{\lst!getInts n = sequence $ replicate n getInt!} % поправка на оцветяването $
\end{itemize}
\item \lst{mapM :: (a -> IO b) -> [a] -> IO [b]}
\begin{itemize}
\item композира списък от трансформации по списък от стойности
\item \lst{mapM = sequence . map}
\item \lst!printRead s = do putStr $ s ++ " = "; getInt! % поправка на оцветяването $
\item \lst{readCoordinates = mapM printRead ["x", "y", "z"]}
\end{itemize}
\item \lst{mapM_ :: (a -> IO b) -> [a] -> IO ()}
\begin{itemize}
\item Също като \lst{mapM}, но изхвърля резултата
\item \lst{printList = mapM_ print}
\end{itemize}
\item \lst{forever :: IO a -> IO b}
\begin{itemize}
\item безкрайна композиция на една и съща трансформация (както \lst{repeat} за списъци)
\item \lst{forever \$ do line <- getLine; putStrLn line}
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}<\switch{part2}>[fragile]
\frametitle{Средно аритметично на числа v2.0}
\small
\begin{lstlisting}
readInt :: String -> IO Int
readInt s = do putStr $ "Моля, въведете " ++ s ++ ": "
getInt
findAverage :: IO Double
findAverage = do n <- readInt "брой"
l <- mapM (readInt.("число #"++).show) [1..n]
let s = sum l
return $ fromIntegral s / fromIntegral n
main = forever $
do avg <- findAverage
putStrLn $ "Средното аритметично е: " ++ show avg
putStrLn "Хайде отново!"
\end{lstlisting}
\end{frame}
\begin{frame}<\switchand{part1}{lazy}>[fragile]
\frametitle{Ленив вход и изход}
\begin{itemize}[<+->]
\item Ленивото оценяване в Haskell ни позволява да работим с входно/изходни потоци
\item \lst{getContents :: IO String} --- връща списък от \alert{всички} символи на стандартния вход
\item списъкът се оценява лениво, т.е. прочита се при нужда
\item \textbf{Пример:}
\begin{lstlisting}
noSpaces = do text <- getContents
putStr $ filter (/=' ') text
\end{lstlisting}
\item \lst{interact :: (String -> String) -> IO ()} --- лениво прилага функция над низове над стандартния вход и извежда резултата на стандартния изход
\item \textbf{Пример:}
\begin{lstlisting}
noSpaces = interact $ filter (/=' ')
\end{lstlisting}
\end{itemize}
\end{frame}
\begin{frame}<\switch{part1}>[fragile]
\frametitle{Работа с файлове}
\begin{itemize}[<+->]
\item \lst{IO} позволява работа с произволни файлове, не само със стандартните вход и изход
\item \lst{import System.IO}
\item \lst{openFile :: FilePath -> IOMode -> IO Handle} --- отваря файл със зададено име в зададен режим
\begin{itemize}
\footnotesize
\item \lst{type FilePath = String}
\item \lst{data IOMode = ReadMode|WriteMode|AppendMode|ReadWriteMode}
\end{itemize}
\item Има варианти на функциите за вход/изход, които работят с \lst{Handle}
\item \lst{hGetLine}, \lst{hGetChar}, \lst{hPutStr}, \lst{hPutStrLn}, \lst{hGetContents}\ldots
\item \textbf{Пример:}
\vspace{-1ex}
\begin{lstlisting}
encrypt cypher inFile outFile =
do h1 <- openFile inFile ReadMode
text <- hGetContents h1
h2 <- openFile outFile WriteMode
hPutStr h2 @\ifbool{app}{\$ }{(}@map cypher text@\ifbool{app}{}{)}@
\end{lstlisting}
\end{itemize}
\end{frame}
\section{Монади}
\begin{frame}<\switch{part2}>
\frametitle{Монади}
\begin{itemize}[<+->]
\item \lst{IO} е пример за \alert{монада}
\item Монадите са конструкции, които „опаковат“ обекти от даден тип
\item \textbf{Примери:}
\begin{itemize}
\item \lst{IO} опакова стойност във входно/изходна трансформация
\item \lst{Maybe} опакова стойност с „флаг“ дали стойността съществува
\item \lst{[a]} опакова няколко „алтернативни“ стойности в едно
\item \lst{r -> a} опакова стойност от тип \lst{a} в „машинка“, която я пресмята при подаден параметър от тип \lst{r}
\item \lst{s -> (a,s)} опакова стойност от тип \lst{a} в „действие“, което променя дадено състояние от тип \lst{s}
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}<\switch{part2}>
\frametitle{Монадни операции}
\begin{itemize}[<+->]
\item \lst{Monad} е клас от \alert{типови конструктори}, които
са монади
\item „Опаковката“ понякога е прозрачна\ldots (пример: \lst{Maybe, [a]})
\item \ldots но често е \alert{еднопосочна}: един път опакована, не можем да извадим стойността извън опаковката\ldots (пример: \lst{IO, r -> a})
\item \ldots но можем да я преопаковаме!
\item \lst{(>>=) :: Monad m => m a -> (a -> m b) -> m b}
\item оператор за „свързване“ на опаковани стойности
\item \lst{b = a >>= f}:
\begin{itemize}
\item поглеждаме стойността \lst{x} в опаковката \lst{a}
\item прилагаме функцията \lst{f} над \lst{x}
\item и получаваме нова опакована стойност \lst{b}
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}<\switch{part2}>[fragile]
\frametitle{Императивен стил чрез монади}
\small
\begin{fixedarea}
\begin{itemize}[<+->]
\item \lst{do} всъщност е синтактична захар за поредица от „свързвания“
\item \textbf{Примери:}
\begin{onlyenv}<2>
\begin{lstlisting}
main = do line <- getLine
putStrLn $ "Въведохте: " ++ line
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<3>
\begin{lstlisting}
main = getLine >>= (\line -> putStrLn $ "Въведохте " ++ line)
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<4->
\begin{lstlisting}
main = getLine >>= putStrLn . ("Въведохте: " ++)
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<5>
\begin{lstlisting}
findAverage = do putStr "Моля, въведете брой: "
n <- getInt
s <- readAndSum n
return $ fromIntegral s / fromIntegral n
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<6->
\begin{lstlisting}
findAverage = putStr "Моля, въведете брой: " >>=
(\_ -> getInt >>=
(\n -> readAndSum n >>=
(\s -> return $ fromIntegral s /
fromIntegral n))
\end{lstlisting}
\end{onlyenv}
\item<7-> работи за произволни монади, не само за \lst{IO}!
\item<8-> позволява абстрахиране от страничните ефекти и моделиране на поредица от инструкции
\item<9-> императивен стил във функционалното програмиране
\end{itemize}
\end{fixedarea}
\end{frame}
\end{document}