-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlec03.hs
384 lines (288 loc) · 18.9 KB
/
lec03.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
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
import Prelude hiding (minimum, repeat, replicate)
-------------------------------------------------
-- Домашнее задание 3
-------------------------------------------------
-- Напишите модуль Homework3 в одноименном файле. Модуль должен
-- экспортировать только функции powerset, minimum, regLengths и
-- primes (см. ниже). Внутри модуля импортируйте Prelude за
-- исключением minimum.
-- 1. Используя генератор списков, напишите функцию powerset :: [a] ->
-- [[a]], которая возвращает список всех подсписков данного списка.
-- Порядок элементов в возвращаемых списках неважен.
-- 2. Конъюнкция и дизъюнкция реализованы в Prelude следующим образом.
-- (&&) :: Bool -> Bool -> Bool
-- True && x = x
-- False && _ = False
--
-- (||) :: Bool -> Bool -> Bool
-- True || _ = True
-- False || x = x
-- Объясните, как будут вычисляться выражения e1 && e2 и e1 || e2 для
-- произвольных выражений e1, e2 и сравните это с вычислением аналогичнх
-- выражений в других языках программирования, таких как C, Java и Python.
-- 3. Функцию minimum можно определить следующим образом.
minimum :: Ord a => [a] -> a
minimum ls = head (msort ls)
-- где msort нужно было написать в домашнем задании 2 (см. ниже). На
-- первый взгляд, это плохая идея, так как msort имеет сложность
-- O(n*log(n)), в то время как minimum должен иметь сложность O(n).
-- Однако из-за ленивой стратегии исполнения Haskell будет выполнять
-- только те сравнения, которые необходимы для получения головы
-- отсортированного списка. Рассмотрим вызов head (msort [2, 9, 1, 6, 4]).
-- Нарисуйте дерево рекурсивных вызовов функции msort и найдите
-- количество выполненных сравнений в функции merge. Определите
-- сложность такого определения minimum в Haskell.
-- 4. Можно доказать, что множество длин всех слов регулярного (или
-- конечно-автоматного) языка есть объединение конечного числа
-- арифметических прогрессий. Используя функцию msort, напишите
-- функцию regLenths :: [(Integer, Integer)] -> [Integer], которая
-- принимает список пар [(a1, d1), ..., (an, dn)] и возвращает
-- объединение арифметических прогрессий с первым членом ai и шагом di
-- для всех i = 1, ..., n. Полученный список должен быть отсортирован
-- по возрастанию и может содержать одинаковые элементы.
-- 5. Напишите функцию primes :: [Integer], которая вычисляет бесконечный
-- список простых чисел с помощью алгоритма "Решето Эратосфена" (см.
-- https://ru.wikipedia.org/wiki/Решето_Эратосфена). Начинать следует
-- с бесконечного списка нечетных чисел, больших 1. Для прореживания
-- списка используйте генератор списков.
-------------------------------------------------
-- Решения домашних заданий 1 и 2
-------------------------------------------------
-- Домашнее задание 1
intToChar :: Int -> Char
intToChar 0 = '0'
intToChar 1 = '1'
intToChar 2 = '2'
intToChar 3 = '3'
intToChar 4 = '4'
intToChar 5 = '5'
intToChar 6 = '6'
intToChar 7 = '7'
intToChar 8 = '8'
intToChar 9 = '9'
intToChar n = error ("intToChar: " ++ show n ++ " is not a decimal digit")
-- Функцию show возвращает текстовое представление значения, тип которого
-- принадлежит классу Show. Членство в этом классе требует наличие такой функции.
-- Тип Int является членом класса Show. О классах типов будем говорить позже.
intToChar' :: Int -> Char
intToChar' n
| 0 <= n && n <= 9 = toEnum (n + 48)
| otherwise = error ("intToChar: " ++ show n ++ " is not a decimal digit")
-- Функция toEnum :: Int -> a определена для типов a из класса Enum.
-- Членство в этом классе требует наличие такой функции.
-- Тип Char является членом класса Enum. О классах типов будем говорить позже.
-- Также есть функции chr, ord в Data.Char
-- Домашнее задание 2
-- Числа Фибоначчи
-- Нехвостовая рекурсия
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
-- Хвостовая рекурсия
-- Два последовательных числа Фибоначчи передаются как второй и третий аргументы
-- Первый аргумент является счетчиком
-- Трассировка:
-- fib 5 0 1 = fib 4 1 1 = fib 3 1 2 = fib 2 2 3 = fib 1 3 5 = fib 0 5 8 = 5
-- Общий вид: fib n f_n f_{n+1}
fibIter n = fib n 0 1 where
fib 0 x _ = x
fib n x y = fib (n - 1) y (x + y)
-- Сортировка слиянием
halve xs = splitAt (div (length xs) 2) xs
-- Сливает два упорядоченных списка
merge :: Ord a => [a] -> [a] -> [a]
merge [] ys = ys
merge xs [] = xs
merge l1@(x : xs) l2@(y : ys)
| x < y = x : merge xs l2
| otherwise = y : merge l1 ys
msort :: Ord a => [a] -> [a]
msort [] = []
msort [x] = [x]
msort xs =
let (l1, l2) = halve xs in
merge (msort l1) (msort l2)
-------------------------------------------------
-- Конспект лекции 3 01.03.2021
-------------------------------------------------
-- Содержание
-- 1. Модули
-- 2. Основы лямбда-исчисления с простыми типами
-- 3. Ленивые вычисления
-- 4. Арифметические прогрессии
-- 5. Генераторы списков
-- Напоминание: хорошим введением в Haskell является книга
-- Макеева, ссылка на которую находится на source.unn.ru.
-------------------------------------------------
-- 1. Модули
-------------------------------------------------
-- (Напоминание) Чтобы загрузить модуль Data.List, поместите в начале программы
-- import Data.List
-- В командной строке GHCi можно также сказать
-- :m + Data.List
-- Чтобы выгрузить модуль Data.List в командной строке GHCi
-- :m - Data.List
-- Имена загруженных модулей пишутся в подсказке GHCi
-- Если функция с одним и тем же именем имеется в двух загруженных
-- модулях (например, в программе и в библиотечном модуле), то GHCi
-- выдает ошибку "Ambiguous occurrence".
-- Чтобы импортировать только функции nub и sort из модуля Data.List:
-- import Data.List (nub, sort)
-- Чтобы импортировать все функции, кроме nub и sort из модуля Data.List:
-- import Data.List hiding (nub, sort)
-- Так же можно поступить с модулем Prelude, который загружается по умолчанию.
-- Это позволяет дать этим функциям свои определения.
-- Чтобы поместить импортируемые функции в другое пространство имен:
-- import qualified Data.List
-- После этого функции будут доступны с префиксом Data.List,
-- например, Data.List.sort.
-- Чтобы поместить импортируемые функции в заданное пространство имен M:
-- import qualified Data.List as M
-- После этого функции будут доступны с префиксом M, например, M.sort.
-- Создание своих модулей
-- Каждый модуль в своем файле
-- Имя модуля = имя файла
-- Расширение файла: .hs
-- Структура файла:
-- module Имя(определение1, определение2,..., определениеN) where
--
-- import Модуль1(...)
-- import Модуль2(...)
--
-- определение1
-- определение2
-- ...
-- После Имя идут экспортируемые определения.
-- Остальные определения в модуле могут использоваться только внутри данного модуля.
-- Если после Имя нет скобок, экспортируются все определения.
-- Модуль содержит определения
-- типов
-- значений (в том числе функций)
-- классов типов
-- экземпляров классов типов
-------------------------------------------------
-- 2. Основы лямбда-исчисления с простыми типами
-------------------------------------------------
-- Из Prelude
-- const :: a -> b -> a
-- const x _ = x
-- Функция, которая принимает аргументы типов a1, ..., an и возвращает
-- аргумент типа b, имеет тип a1 -> ... -> an -> b. Однако это не есть
-- примитивное выражение. На самом деле a1 -> a2 -> ... -> an -> b =
-- a1 -> (a2 -> (... -> (an -> b)...)). Таким образом, типы
-- определяются рекурсивно:
-- (1) Int, Char, Bool, ... -- это типы;
-- (2) если a и b -- типы, то a -> b -- также тип.
-- Это упрощение, так как в Haskell есть также полиморфные типы,
-- алгебраические типы, классы типов и др.
-- Значит, любая функция в Haskell есть функция одного аргумента. Но
-- она может возвращать функцию, которая принимает следующий аргумент
-- и т.д. Для того, чтобы это объяснить более подробно, полезно
-- научиться отличать по записи выражения, которые можно напечатать,
-- от функций. Пусть, например, x :: Int и y :: Int. Тогда y^x :: Int.
-- Это выражение можно рассмотривать как функцию от y типа Int -> Int.
-- Эта функция записывается \y -> y^x. При фиксированном x она
-- принимает y и возвращает y^x. Например, при x = 2 это функция
-- возведения в квадрат. Если x :: Int, то \y -> y^x :: Int -> Int.
-- Аналогично можно рассмотреть функцию \x -> y^x :: Int -> Int при
-- фиксированном y :: Int. Это функция, возвращающая произвольную
-- степень y. Наконец, y^x можно рассмотреть как функцию и от x, и от
-- y: она записывается \x -> \y -> y^x, или сокращенно \x y -> y^x.
-- Это функция, которая принимает x и возвращает функцию, которая
-- превращает y в y^x. Таким образом, \x y -> y^x :: Int -> (Int -> Int).
-- Отличие в синтаксисе обычного выражения от функции является
-- основной идеей лямбда-исчисления -- математического аппарата,
-- на котором основан любой язык функционального программирования.
-- Лямбда-исчисление разработал американский математик Алонзо Чёрч.
-- Определение const выше эквивалентно следующим.
-- const x = \y -> x
-- const = \x y -> x
-- const = \x -> \y -> x
-- Это функция, которая принимает x и возвращает функцию-константу,
-- которая на любом аргументе возвращает x.
-------------------------------------------------
-- 3. Ленивые вычисления
-------------------------------------------------
-- undefined :: a
-- Это значение, вычисление которого вызывает исключение. Здесь a на
-- самом деле означает ∀a. a и означает, что undefined может иметь
-- любой тип. Таким образом, undefined можно помещать в любой
-- контекст, и это не приводит к ошибкам типа во время исполнения,
-- потому что undefined не возвращает никакого значения.
-- error :: [Char] -> a
-- Принимает сообщение об ошибке и вызывает исключение, которое
-- печатает это сообщение. Также возвращает произвольный тип, что
-- нормально, так как error не возвращает значение.
-- В отличие от большинства популярных языков программирования Haskell
-- использует ленивую стратегию вычисления. Выражение вычисляется
-- только тогда, когда его значение требуется, чтобы определить
-- дальнейший ход выполнения программы, или когда его надо напечатать.
-- Следующие вызовы не вызывают исключение, потому что ненужные
-- подвыражения не вычисляются.
-- const "hello" undefined
-- length [undefined]
-- let f (_, _) = 1 in f (undefined, 2)
-- Ленивая стратегия также позволяет строить бесконечные структуры.
-- Бесконечный список из x
repeat :: a -> [a]
repeat x = x : repeat x
-- take n l возвращает первые n элементов списка l
-- take 5 (repeat 1)
-- replicate n x возвращает список длины n, состоящий из x.
-- replicate через repeat
replicate n x = take n (repeat x)
naturals = naturalsFrom 1
naturalsFrom n = n : naturalsFrom (n + 1)
factorial n = product (take n naturals)
-- Что будет, если функции дать бесконечный список?
-- 1. Вернет конечный результат. Пример: take
-- 2. Вернет бесконечный список, с которым можно работать,
-- если не вычислять его целиком.
-- Пример: take 3 (drop 2 naturals)
-- 3. Не остановится. Пример: length naturals
-- Вычисление выражения в интерпретаторе вызывает функцию show, которая
-- преобразовывает полученное значение в строчку. Значение
-- анализируется целиком, поэтому вычисление в командной строке
-- (at the prompt) энергичное, а не ленивое.
-------------------------------------------------
-- 4. Арифметические прогрессии
-------------------------------------------------
-- [1..10] -> [1,2,3,4,5,6,7,8,9,10]
-- [1,3..10] -> [1,3,5,7,9]
-- [10,9..1] -> [10,9,8,7,6,5,4,3,2,1]
-- [1..] -> [1,2,3,4,5,6,7,8,9,10,...]
-- Начало, разность и конец не обязаны быть константами
arithSeq start diff end = [start, start + diff .. end]
-- работает также с символами
-- ['A'..'Z']
-- и с Float и Double (разность по умолчанию равна 1)
-- В общем случае эта запись работает с типами, принадлежащими классу типов Enum
-- Еще один вариант факториала
-- factorial n = product [1..n]
-- Вообще написание различных версий факториала является национальным
-- спортом программистов на Haskell.
-------------------------------------------------
-- 5. Генераторы списков
-------------------------------------------------
-- Также используются названия: замыкания списков, абстракция списков,
-- списковое включение. Англ. list comprehension, по аналогии с set
-- comprehension.
-- > [x^2 | x <- [1..10]]
-- [1,4,9,16,25,36,49,64,81,100]
-- > [(x,y) | x <- [1..4], y <- [1..4], x < y]
-- [(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]
-- В общем случае:
-- [выражение | образец <- список, образец <- список, ... , условие, условие, ...]
-- Пример: быстрая сортировка
qsort [] = []
qsort (x : xs) =
qsort [y | y <- xs, y <= x] ++ [x] ++ qsort [y | y <- xs, y > x]
-- Следующее определение проще написать, чем понять, как оно работает.
-- Во всяком случае, оно правильно с математической точки зрения.
fibs = 0 : 1 : [x + y | (x, y) <- zip fibs (tail fibs)]
-- или
-- fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- 0 1 1 2 3 5 8 13 21 34 55
-- 1 1 2 3 5 8 13 21 34 55 ...
----------------------------------------
-- 1 2 3 5 8 13 21 34 55 89 ...