-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathlecture6_list_hof.tex
183 lines (169 loc) · 10.5 KB
/
lecture6_list_hof.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
\documentclass[11pt]{beamer}
\input{lecture_preamble.tex}
\title{Лекция 6: работа со списками}
\begin{document}
\begin{frame}[plain]
\maketitle
\end{frame}
\begin{frame}[fragile]
\frametitle{Списки в Haskell}
\begin{itemize}
\item Поговорим о списках подробнее.
\item Это структура данных, которая постоянно встречается в программах.
\item Но по поведению и характеристикам \emph{очень сильно} отличается от списков в Java и C\# (или \haskinline|std::vector| в C++).
\pause
\item Списки в Haskell и множестве других языков, начиная с Lisp "--- односвязные.
\pause
\item То есть вместе с каждым элементом хранится ссылка на остальную часть списка.
\item Вспомним определение:
\begin{haskell}
data [a] = [] | a : [a]
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Некоторые функции первого порядка над\\ списками}
\begin{itemize}
\item \haskinline|(++) :: [a] -> [a] -> [a]| \pause"--- объединение.
\item \haskinline[escapeinside=``]|(!!) :: [a] -> Int -> a| \pause"--- элемент по индексу.
\item \haskinline|null :: a -> Bool| \pause"--- проверка на пустоту (лучше, чем \haskinline|length xs == 0|! Но сопоставление ещё лучше).
\item \haskinline|elem :: Eq a => a -> [a] -> Bool| \pause"--- проверка на вхождение элемента.
\item \haskinline|and, or :: [Bool] -> Bool| \pause"--- все ли элементы \haskinline|True| (или есть ли такой).
\pause
\item У части из них тип на самом деле более общий, но пока нам не нужен.
\pause
\item Все основные в \href{https://hackage.haskell.org/package/base/docs/Data-List.html}{Data.List}. \pause И ещё пакет \hackage{safe}.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Некоторые функции второго порядка над\\ списками}
\begin{itemize}
\item \haskinline|map :: (a -> b) -> [a] -> [b]| \pause"--- применяет функцию ко всем элементам.
\item \haskinline|filter :: (a -> Bool) -> [a] -> [a]| \pause"--- выбирает элементы, удовлетворяющие условию.
\item \haskinline|concatMap :: (a -> [b]) -> [a] -> [b]| \pause"--- применяет функцию ко всем элементам и объединяет результаты.
\item \haskinline|all, any :: (a -> Bool) -> [a] -> Bool| \pause"--- все ли элементы удовлетворяют условию (или есть ли такой).
\pause
\item[]
\item Упражнение: реализуем какие-нибудь из них (рекурсивно и друг через друга).
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Сложность функций над списками}
\begin{itemize}
\item Базовые операции "--- конструирование списков и их разбор через \haskinline|:|.
\pause
\item Поэтому \haskinline|length| (а значит, и \haskinline|length xs == 0|) проходит весь список целиком, а \haskinline|null| нет.
\pause
\item \haskinline[escapeinside=``]|xs !! n| "--- цена пропорциональна \pause\haskinline|n|.
\pause
\item Чем определяется цена \haskinline|xs ++ ys|? \pause Длиной \haskinline|xs|.
\pause
\item Добавление в конец списка \haskinline|xs ++ [y]| получается дорогим.
\item В цикле может сделать линейный алгоритм квадратичным.
\pause
\item Вместо добавления в конец часто лучше добавлять в начало, а потом развернуть.
\pause
\item Всё это осложняется ленивостью, о ней позже.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Выделения списков (list comprehensions)}
\begin{itemize}
\item Есть удобный способ записи списков, похожий на выражения для множеств.
\item[] \haskinline![x*x | x <- [1..5], even x] == !\pause \haskinline|[4, 16]|.
\item Как \(\left\{x^2\,\middle|\,x \in \{1, \ldots, 5\},\,even(x)\right\}\).
\item \haskinline!x <- [1..5]! "--- \emph{генератор}, \haskinline|even x| "--- \emph{фильтр}.
\pause
\item Тех и других может быть много.
\item Генераторы могут использовать переменные, введённые в предыдущих. Пример:
\begin{haskell}
[(x, y) | x <- [1..5], y <- [1..x], odd (x - y)] == !\pause!
[(3, 2), (5, 2), (5, 4)]
\end{haskell}
\item Эти выражения преобразуются в комбинации \haskinline|concatMap|, \haskinline|map| и \haskinline|filter|:
\begin{haskell}
[x*x | x <- [1..5], even x] == !\pause!
map (\x -> x*x) (filter even [1..5])
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Правая свёртка списков и структурная рекурсия}
\begin{itemize}
\item При \emph{структурной рекурсии} рекурсивный вызов делается на подтерме одного из аргументов функции (например, хвосте списка).
\item Большинство наших рекурсивных определений выглядели именно так.
\pause
\item Оказывается, что есть универсальная функция, через которую выражаются такие определения:
\begin{haskell}
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [x1, x2, ..., xn] ==
x1 `f` (x2 `f` ... (xn `f` z)...)
\end{haskell}
\item Правая свёртка проходит по структуре списка и заменяет \haskinline{[]} на \haskinline{z} и \haskinline{(:)} на \haskinline{f}.
\item Например, \haskinline|sum| получится, если взять \haskinline{f =!\pause! (+)} и \haskinline{z =!\pause! 0}.
\item Упражнение: реализовать \haskinline|length|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Левая свёртка и хвостовая рекурсия}
\begin{itemize}
\item При \emph{хвостовой рекурсии} рекурсивный вызов делается в самом конце вычисления. Например,
\item \begin{haskellsmall}
sum xs = sum' 0 xs where
sum' acc [] = acc
sum' acc (x:xs) = sum' !\pause!(acc + x) xs
\end{haskellsmall}
\item Универсальная функция для таких определений:
\begin{haskellsmall}
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl _f z [] = z
foldl f z (x:xs) = foldl f !\pause!(f z x) xs
\end{haskellsmall}
\item Она проходит по списку слева и получается
\begin{haskellsmall}
(...((z `f` x1) `f` x2) `f`...) `f` xn
\end{haskellsmall}
\item Хвостовая рекурсия может быть скомпилирована в цикл и в других языках ФП очень важна. В Haskell ленивость всё усложняет.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Свёртки других типов данных}
\begin{itemize}
\item Сравним объявление списка с типом \haskinline|foldr|:
\begin{haskellsmall}
data [a] = a : [a] | []
foldr :: (a -> b -> b) -> b -> [a] -> b
\end{haskellsmall}
\item Видим, что у нас по одному аргументу для каждого конструктора: каждый принимает аргументы типов полей этого конструктора (а если там \haskinline|[a]|, то \haskinline|b|) и возвращает \haskinline|b|.
\item Последний аргумент "--- разбираемый список.
\item Для \haskinline|Maybe|:
\begin{haskellsmall}
data Maybe a = Nothing | Just a
foldMaybe :: ? -> ? -> ? -> b !\pause!
foldMaybe :: b -> (a -> b) -> Maybe a -> b
\end{haskellsmall}
\pause
\item Это \haskinline|Data.Maybe.maybe| с обратным порядком аргументов.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Свёртка деревьев}
\begin{itemize}
\item Для деревьев возьмём пример, где данные хранятся в листьях, но не в ветвях:
\begin{haskellsmall}
data Tree a = Leaf a | Node (Tree a) (Tree a)
foldTree :: ? -> ? -> Tree a -> b !\pause!
foldTree :: (a -> b) -> (b -> b -> b) -> Tree a -> b !\pause!
sumTree = foldTree!\pause! id (+)
\end{haskellsmall}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Дополнительное чтение}
\begin{itemize}
\item \href{https://habr.com/ru/post/667058/}{Применение обобщённой свёртки для обработки синтаксических деревьев}
\item \href{http://www.cs.nott.ac.uk/~pszgmh/fold.pdf}{A tutorial on the universality and expressiveness of fold}
\item \href{https://wiki.haskell.org/wikiupload/1/14/TMR-Issue6.pdf}{Getting a Fix from the Right Fold} (реализация \haskinline{dropWhile} через \haskinline{foldr})
\end{itemize}
\end{frame}
\end{document}