-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlec04.hs
227 lines (154 loc) · 10.3 KB
/
lec04.hs
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
import Prelude hiding ((.), flip, curry, uncurry, ($), map, filter)
-------------------------------------------------
-- Домашнее задание 4
-------------------------------------------------
-- 1. Объясните, почему функции в каждой паре совпадают.
-- (1) \x y -> x + 1 и const . (+ 1)
-- (2) \x y = y x и flip id
-- Функция id определена в Prelude следующим образом.
-- id :: a -> a
-- id x = x
-- 2. Объясните, почему map (f . g) == map f . fmap g.
-- 3. Что делают следующие два определения?
listOfFuns = map (*) [0..]
applyAll x = take 10 (map ($ x) listOfFuns)
-- 4. Напишите функцию iter :: (a -> a) -> Integer -> (a -> a),
-- такую что iter f n x = f (f (... (f x)...)) (f используется n раз, n >= 0).
-- Замените undefined на правильные выражения.
iter f 0 = undefined
iter f n = undefined
-- iter можно также определить с помощью следующей функции из Prelude.
-- iterate :: (a -> a) -> a -> [a]
-- iterate f x == [x, f x, f (f x), ...]
-- Тогда
-- iter f n x = iterate f x !! n
-- 5. Дана функция
next :: Int -> Int
next x
| x `mod` 2 == 0 = x `div` 2
| otherwise = 3 * x + 1
-- Итерация функции next генерирует последовательность Коллатца.
-- См. "Гипотеза Коллатца" в Википедии и https://xkcd.com/710.
-- С помощью функций iterate, takeWhile и length из Prelude напишите
-- функцию collatzLength :: Int -> Int, такую что collatzLength n
-- возвращает длину последовательность Коллатца, начинающуюся c n и
-- заканчивающуюся 1. Гипотеза состоит в том, что collatzLength
-- определена на всех натуральных числах.
-- Напишите функцию longCollatz :: Int -> Int -> Int -> Int, такую что
-- longCollatz a b n возвразает количество последовательностей Коллатца,
-- начинающихся с чисел от a до b включительно и имеющих длину не меньше n.
-- Для этого можно сделать следущее.
-- (1) Найти список l натуральных чисел от a до b.
-- (2) Для каждого элемента l вычислить длину последовательности, которая
-- начитается с этого элемента.
-- (3) Из полученного списка длин оставить элементы >= n.
-- (4) Найти длину оставшегося списка.
-------------------------------------------------
-- Конспект лекции 4 от 08.03.2021
-------------------------------------------------
-- Напоминание: темы, рассматриваемые ниже, хорошо описаны в книге
-- Макеева, ссылка на которую находится на source.unn.ru.
-- Содержание
-- 1. Бесточечная запись
-- 2. Функции высших порядков
-------------------------------------------------
-- 1. Бесточечная запись
-------------------------------------------------
-- Если объявление функции имеет вид
-- f x = expression x -- определение (1)
-- то достаточно написать
-- f = expression -- определение (2)
-- Поскольку выражения Haskell являются математическими выражениями,
-- для них выполняется закон замены равного равным: если e1 == e2, то
-- e1 можно заменить на e2 в любом контексте. Так, при наличии
-- определения (2) функция f заменяется на expression в выражении f x
-- и получается expression x, то есть эффект определения (2) такой же,
-- как и определения (1).
-- Запись (2) называется бесточечной (безаргументной). Здесь под "точкой"
-- имеется в виду значение, принимаемое функцией, по аналогии с точками
-- топологического пространства. Пример (лекция 1):
-- inc = (+) 1
-- вместо
-- inc x = (+) 1 x
-- Бесточечная запись предлагает думать о функциях и их композициях
-- вместо данных.
-------------------------------------------------
-- 2. Функции высших порядков
-------------------------------------------------
-- Главная особенность функционального программирования состоит в том,
-- что функции являются значениями первого класса. С ними можно делать
-- то же, что и с обычными значениями, такими как числа, символы,
-- булевы значения, списки, кортежи и т.п. Именно, функции можно
-- передавать в другие функции в качестве аргумента, возвращать их из
-- функций, помещать в структуры данных и т.д.
-- Функции, которые принимают или возвращают функции, называются
-- функциями высших порядков. В математике они часто называются
-- функционалами или операторами. Пример: операторы дифференцирования
-- и взятия первообразной. Лекция 3 говорит, что любая функция
-- двух или более аргументов является функцией высшего порядка,
-- потому что она принимает первый аргумент и возвращает функцию.
-- Более существенным проявлением высшего порядка являются
-- функции, которые принимают другие функции в качестве аргументов.
-- Композиция функций f и g обозначается f . g по аналогии с f o g
-- в математике.
-- Joke: you may be a mathematician if you think fog is a composition.
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f (g x)
-- Следующее определение эквивалентно предыдущему
-- (.) f g = \ x -> f (g x)
-- Пример: not . null :: [a] -> Bool проверяет список на непустоту.
-- Другие стандартные фукнции высших порядков
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
-- Следующее определение эквивалентно предыдущему
-- flip f = \x -> \y -> f y x
-- Примеры применения flip
-- Возведение в куб (бесточечная запись)
cube = flip (^) 3
-- В Prelude объявлена функция elem :: Eq a => a -> [a] -> Bool,
-- которая проверяет, является ли первый аргумент элементом второго.
-- Тип a должен поддерживать сравнение.
isUpperAlpha :: Char -> Bool
isUpperAlpha x = elem x ['A'..'Z']
-- Альтернативно (бесточечная запись):
-- isUpperAlpha = flip elem ['A'..'Z']
-- Действительно,
-- isUpperAlpha x = flip elem ['A'..'Z'] x = elem x ['A'..'Z']
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry f (x, y) = f x y
-- Если через U -> V обозначить множество функций из U в V, а через
-- U x V обозначить декартово произведение U и V, то функции curry и
-- uncurry являются изоморфизмом и его обратным между множествами
-- (A x B) -> C и A -> (B -> C).
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x : xs) = (f x) : map f xs
-- map f [x1, ..., xn] = [f x1, ..., f xn]
filter :: (a -> Bool) -> [a] -> [a]
filter p [] = []
filter p (x:xs) | p x = x : filter p xs
| otherwise = filter p xs
-- Альтернативно: filter p xs = [x | x <- xs, p x]
-- map не меняет длину списка, поэтому filter нельзя выразить через map.
-- Пример
-- Пусть есть предикат prime :: Int -> Bool.
-- head (filter prime [100000, 99999..]) есть наибольшее простое число,
-- не превосходящее 100000. Вычисление останавливается после нахождения
-- такого простого числа.
($) :: (a -> b) -> a -> b
($) f x = f x
-- Зачем нужен оператор ($), если он просто выражает аппликацию? Дело
-- в том, что применение функции левоассоциативно и имеет наивысший
-- приоритет. Напротив, оператор $ правоассоциативный и имеет низший
-- приоритет. Поэтому вместо f x (g y (h z)) можно написать
-- f x $ g y $ h z.
-- Пример
-- sum (filter (> 10) (map (*2) [2..10]))
-- можно переписать с меньшим количеством скобок:
-- sum $ filter (> 10) $ map (*2) [2..10]
-- takeWhile :: (a -> Bool) -> [a] -> [a]
-- возвращает наибольший префикс списка, все элементы которого
-- удовлетворяют предикату.
-- takeWhile (/= ' ') "Первое слово в предложении" => "Первое"