-
Notifications
You must be signed in to change notification settings - Fork 10
/
lecture5_hof.tex
353 lines (326 loc) · 17.1 KB
/
lecture5_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
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
\documentclass[10pt]{beamer}
\input{lecture_preamble.tex}
\title{Лекция 5: функции как значения}
\begin{document}
\begin{frame}[plain]
\maketitle
\end{frame}
\begin{frame}[fragile]
\frametitle{Функции как значения}
\begin{itemize}
\item Как упоминалось в начале курса, одно из оснований ФП состоит в том, что функции могут использоваться как значения.
\item В Haskell можно выразиться сильнее:\pause
\item Функции это и есть просто значения, тип которых имеет форму \haskinline|ТипПараметра -> ТипРезультата| для каких-то \haskinline|ТипПараметра| и \haskinline|ТипРезультата|.\pause
\item Мы уже видели примеры этого в равноправии функций и \only<4>{\textbf{других} }переменных.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Функции высших порядков}
\begin{itemize}
\item В частности, функции могут принимать на вход функции.
\item То есть тип параметра сам может быть функциональным типом.
\item Тривиальный пример:
\begin{haskell}
foo :: (Char -> Bool) -> Bool
foo f = f 'a'
ghci> foo isLetter !\pause!
True
\end{haskell}
\item Скобки вокруг типа параметра здесь необходимы.\pause
\item Функции, параметры которых "--- функции, называются \emph{функциями высших порядков (ФВП)}.\pause
\item Часто ими также считают функции, возвращающие функции, но не в Haskell (скоро увидим почему).
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Лямбда-выражения}
\begin{itemize}
\item В \haskinline|foo| с прошлого слайда можем также передать свою новую функцию, определив её локально:
\begin{haskell}
foo isCyrillic where
isCyrillic c = let lc = toLower c
in 'а' <= lc && lc <= 'я'
\end{haskell}
\pause
\item Но имя этой функции на самом деле не нужно.
\item Как и вообще функциям, которые создаются только как аргументы для других (или как результаты).\pause
\item Вместо этого зададим её через \emph{лямбда-выражение}
\begin{haskell}
foo (\c -> let lc = toLower c
in 'а' <= lc && lc <= 'я')
\end{haskell}
\pause
\item[]
\item Кстати, почему это определение неверно?
\pause
\item Как ни странно, в Юникоде \haskinline|'ё' > 'я'|.
\item И не все буквы кириллицы используются в русском.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Лямбда-выражения}
\begin{itemize}
\item Вообще, два определения
\begin{haskell}
функция образец = результат
функция = \образец -> результат
\end{haskell}
эквивалентны.\pause
\item Одно исключение: \href{https://wiki.haskell.org/Monomorphism_restriction}{для второго может быть выведен менее общий тип}.\pause
\item Лямбда-выражение для функции с несколькими параметрами пишется
\begin{haskell}
\образец1 ... образецN -> результат
\end{haskell}
\pause
\item Например,
\begin{haskell}
Data.List.sortBy (\x y -> compare y x) list
\end{haskell}
\item Что делает это выражение?\pause
\item Сортирует список по убыванию.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Лямбда-выражения и \haskinline|case|}
\begin{itemize}
\item Если в обычном определении функции несколько уравнений, например
\begin{haskell}
not True = False
not False = True
\end{haskell}
то в лямбда-выражении придётся использовать \haskinline|case|:\pause
\begin{haskell}
not = \x -> case x of
True -> False
False -> True
\end{haskell}
\pause
или с расширением \haskinline|LambdaCase|
\begin{haskell}
not = \case
True -> False
False -> True
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Несколько стандартных ФВП}
\begin{itemize}
\item В \haskinline|Prelude| есть три функции второго порядка, которые очень часто встречаются в коде Haskell:
\begin{haskell}
($) :: (a -> b) -> a -> b
f $ x =!\pause! f x!\pause!
(.) :: (b -> c) -> (a -> b) -> (a -> c)
g . f =!\pause! \x ->!\pause! g (f x)
flip :: (a -> b -> c) -> (b -> a -> c)
flip f = !\pause! \y x ->!\pause! f x y
\end{haskell}
\item И ещё две в \haskinline|Data.Function|:
\begin{haskell}
(&) = flip (.)
(&) ::!\pause! (a -> b) -> (b -> c) -> (a -> c)
on :: (b -> b -> c) -> (a -> b) ->
(a -> a -> c)
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Избавление от скобок с помощью \haskinline|$| и \haskinline|.|}
\begin{itemize}
\item Казалось бы, в чём смысл \haskinline|$|: зачем писать \haskinline|f $ x| вместо \haskinline|f x|?\pause
\item Этот оператор имеет минимальный возможный приоритет, так что \haskinline|f (x + y)| можно записать как \haskinline|f $ x + y|.\pause
\item И он правоассоциативен, так что \haskinline|f (g (h x))| можно записать как \haskinline|f $ g $ h x|.\pause
\item Но более принято \haskinline|f . g . h $ x|.\pause
\item \haskinline|.| тоже правоассоциативен, но уже с максимальным приоритетом.\pause
\item[]
\item Пока, наверное, проще читать и писать код со скобками, но стандартный стиль Haskell предпочитает их избегать.
\item Только не перестарайтесь!
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Правда о функциях многих переменных}
\begin{itemize}
\item Настала пора раскрыть тайну функций многих переменных в Haskell:\pause
\item Их не существует.\pause
\item \haskinline|->| "--- правоассоциативный оператор, так что \haskinline|Тип1 -> Тип2 -> Тип3| это на самом деле \haskinline|Тип1 -> (Тип2 -> Тип3)|: функция, возвращающая функцию.\pause
\item \haskinline|\x y -> результат| это сокращение для \haskinline|\x -> \y -> результат|, а со скобками \haskinline|\x -> (\y -> результат)|.\pause
\item Применение функций (не \haskinline|$|, а пробел), наоборот, левоассоциативно. То есть \haskinline|f x y| читается как \haskinline|(f x) y|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Каррирование}
\begin{itemize}
\item Обычно в математике для сведения функций нескольких аргументов к функциям одного аргумента используется декартово произведение: $A \times B \to C$, а не $A \to (B \to C)$.\pause
\item В Haskell тоже можно было бы писать
\begin{haskell}
foo :: (Int, Int) -> Int
foo (x, y) = x + y
\end{haskell}
для определения функций и \haskinline|foo (1, 2)| для вызова.\pause
\item Но Haskell здесь следует традиции $\lambda$-исчисления.\pause
\item Эти подходы эквивалентны, так как множества $A \times B \to C$ и $A \to (B \to C)$ всегда изоморфны \pause ($C^{A \cdot B} = C^{B^A}$).
\item В Haskell этот изоморфизм реализуют функции\pause
\begin{haskell}
curry :: ((a, b) -> c) -> a -> b -> c
uncurry :: (a -> b -> c) -> (a, b) -> c
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Частичное применение}
\begin{itemize}
\item Преимущество каррированных функций в том, что естественным образом появляется частичное применение.
\item То есть мы можем применить функцию \enquote{двух аргументов} только к первому и останется функция одного аргумента:
\begin{haskell}
ghci> :t sortBy
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
ghci> :t sortBy (flip compare) !\pause!
sortBy (flip compare) :: Ord a => [a] -> [a]
\end{haskell}
\pause
\item Заметьте, что здесь частичное применение в двух местах: \pause\haskinline|flip| можно теперь рассматривать как функцию трёх аргументов!
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Сечения операторов}
\begin{itemize}
\item Для применения бинарного оператора только к первому аргументу можно использовать его префиксную форму:
\begin{haskell}
ghci> :t (+) 1
(+) 1 :: Num a => a -> a
\end{haskell}
\pause
\item А ко второму? Можно использовать лямбду
\begin{haskell}
\x -> x / 2
\end{haskell}
или \haskinline|flip|
\begin{haskell}
flip (/) 2
\end{haskell}
\pause
\item Но есть специальный синтаксис \haskinline|(арг оп)| и \haskinline|(оп арг)|:
\begin{haskell}
ghci> (1 `div`) 2 !\pause!
0
ghci> (/ 2) 4 !\pause!
2.0
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Сечения кортежей}
\begin{itemize}
\item Расширение \haskinline|TupleSections| позволяет частично применять конструкторы кортежей:
\begin{haskell}
ghci> :set -XTupleSections
ghci> (, "I", , "Love", True) 1 False !\pause!
(1,"I",False,"Love",True)
ghci> :t (, "I", , "Love", True) !\pause!
(, "I", , "Love", True)
:: t1 -> t2 -> (t1, [Char], t2, [Char], Bool)
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{$\eta$-эквивалентность (сокращение аргументов)}
\begin{itemize}
\item Рассмотрим два определения
\begin{haskell}
foo' x = foo x
-- или foo' = \x -> foo x
\end{haskell}
\item \haskinline|foo' y == foo y|, какое бы \haskinline|y| (и \haskinline|foo|) мы не взяли.\pause
\item Поэтому мы можем упростить определение:
\begin{haskell}
foo' = foo
\end{haskell}
\pause
\item Это также относится к случаям, когда совпадают часть аргументов в конце:
\begin{haskell}
foo' x y z w = foo (y + x) z w
\end{haskell}
эквивалентно
\begin{haskell}
foo' x y = foo (y + x)
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{$\eta$-эквивалентность (сокращение аргументов)}
\begin{itemize}
\item И когда само определение такой формы не имеет, но может быть к ней преобразовано
\begin{haskell}
root4 x = sqrt (sqrt x)
\end{haskell}
можно переписать как
\begin{haskell}
root4 x = (sqrt . sqrt) x
root4 = sqrt . sqrt
\end{haskell}
\item Ещё пример:
\begin{haskell}
($) :: (a -> b) -> a -> b
f $ x = f x
($) f x = f x !\pause!
($) f = f !\pause!
($) f = id f !\pause!
($) = id
\end{haskell}
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Бесточечный стиль}
\begin{itemize}
\item Как видим, с помощью композиции и других операций можно дать определение некоторых функций без использования переменных.
\item Это называется \emph{бесточечным стилем} (переменные рассматриваются как точки в пространстве значений).\pause
\item Оказывается, что очень многие выражения в Haskell имеют бесточечный эквивалент.
\item На \href{https://wiki.haskell.org/Pointfree}{Haskell Wiki} можно найти инструменты, позволяющие переводить между стилями.
\item Опять же, не перестарайтесь:
\begin{haskell}
> pl \x y -> compare (f x) (f y)
((. f) . compare .)
\end{haskell}
\pause
\item В языках семейств Forth и APL бесточечный стиль является основным.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Программирование, направляемое типами}
\begin{itemize}
\item При реализации полиморфных функций очень часто их тип подсказывает реализацию.
\item Пример:
\begin{haskell}
foo :: (a -> b, b -> Int) -> (a -> Int)
\end{haskell}
Какую часть реализации можно написать сразу?
\begin{haskell}
foo !\pause!(f, g) = !\pause!\x ->!\pause! ?
\end{haskell}
Типы переменных:\pause
\begin{haskell}
f :: a -> b, g :: b -> Int, !\pause!x :: a, ? :: Int
\end{haskell}
Можно увидеть, что \haskinline|?| может быть \haskinline|g ??|, где \haskinline|?? :: |\pause\haskinline|b = |\pause\haskinline|f x|.
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\frametitle{Типизированные дыры в коде}
\begin{itemize}
\item Эти рассуждения не обязательно делать вручную.
\item Если в выражении (а не в образце) использовать дыру \haskinline|_| (или \haskinline|_название|), то увидим ожидаемый в этом месте тип.
\item
\begin{haskell}
foo :: (a -> b, b -> Int) -> (a -> Int)
foo (f, g) = \x -> g _
\end{haskell}
выдаст сообщение
\begin{ghci}
Found hole `_' with type: b
Where: `b' is a rigid type variable bound by
the type signature for foo :: (a -> b, b -> Int) -> (a -> Int)
Relevant bindings include ...
\end{ghci}
\item Дыр может быть несколько.
\end{itemize}
\end{frame}
\end{document}