Основы функционального программирования/Основы языка Haskell: различия между версиями
Содержимое удалено Содержимое добавлено
м Шаблон со списком лекций |
Ramir (обсуждение | вклад) Нет описания правки |
||
Строка 1:
{{ОФП Содержание}}
= Лекция 4.
Настоящая лекция будет полностью посвящена синтаксису языка [[w:Haskell|Haskell]] (далее, для удобства, «Хаскель»). Будут рассмотрены все важнейшие понятия языка, их соотношение с уже изученными понятиями (на основе абстрактного функционального языка). Также по возможности будут приводиться примеры на
== Структуры данных и их типы ==
Одна из
Символы чаще всего
Списочные структуры в
Как говорилось во [[Основы функционального программирования/Вводная лекция|вводной лекции]], типы данных в функциональных языках определяются автоматически. Механизм автоматического определения
<code>5 :: Integer</code>
Это будет читаться как «Числовая константа 5 имеет тип
== Соглашения по именованию ==
В
== Определители списков и математические последовательности ==
Пожалуй,
<code>[ x | x <- xs ]</code>
<code>[ x | x <- xs, x > m, x < n ]</code>
При помощи двух точек можно также определять любую [[w:арифметическая прогрессия|арифметическую прогрессию]], конечную или [[w:бесконечность|бесконечную]]. Для конечной задаются первый и последний элементы. Разность арифметической прогрессии вычисляется на основе первого и второго заданного элементов — в приведенных выше примерах разность в первой прогресси равна 1, а во второй — 2. Чтобы определить список всех нечётных натуральных чисел вплоть до 10, надо записать: <code>[1, 3 .. 10]</code>. Результатом будет список <code>[1, 3, 5, 7, 9]</code>.
Бесконечные структуры данных можно определять на основе бесконечных списков, а можно использовать механизм [[w:рекурсия|рекурсии]]. Рекурсия в этом случае используется как обращение к рекурсивным функциям. Третий способ создания бесконечных структур данных состоит в использовании бесконечных типов.
'''Пример 11. Определение типа для представления двоичных деревьев.'''
<code>data Tree a = Leaf a
| Branch (Tree a) (Tree a)
Branch :: Tree a -> Tree a -> Tree a
Leaf :: a -> Tree a</code>
В этом примере показан способ определения бесконечного типа. Видно, что без рекурсии тут не обошлось.
ones = 1 : ones
Строка 62 ⟶ 59 :
squares = map (^2) (numbersFrom 0)
Первая функция определяет бесконечную последовательность, полностью состоящую из [[w:1 (число)|единиц]]. Вторая функция возвращает последовательность целых чисел, начиная с заданного. Третья возвращает бесконечную последовательность квадратов натуральных чисел вместе с нулем.
== Вызовы функций ==
Математическая нотация вызова функции традиционно полагала заключение параметров вызова в скобки. Эту традицию впоследствии переняли практически все императивные языки.
В
add :: Integer -> Integer -> Integer
Строка 75 ⟶ 72 :
То ее вызов с конкретными параметрами (например, 5 и 7) будет выглядеть как:
<code>add 5 7</code>
Здесь видно, что нотация
<code>add (x, y) = x + y</code>
Можно видеть, что последняя запись — это функция с одним аргументом в строгой
<code>inc :: Integer -> Integer</code>
<code>inc = add 1</code>
Т.е. в этом случае вызов функции <code>inc</code> с одним параметром просто приведет к вызову функции <code>add</code> с двумя, первый из которых — 1. Это интуитивное понимание понятия частичного применения. Для закрепления понимания можно рассмотреть классический пример
<code>map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x) : (map f xs)</code>
Как видно, здесь использована инфиксная запись операции <code>prefix</code> — двоеточие, только такая запись используется в нотации
<code>map (add 1) [1, 2, 3, 4]</code>
который даст список <code>[2, 3, 4, 5]</code>.
== Использование λ-исчисления ==
'''Пример 12. Функции add и inc, определённые через λ-абстракции.'''
<!-- Неплохо бы для примеров сделать заголовки. Пока отложу -->
<code>add = \x y -> x + y
inc = \x -> x + 1</code>
'''Пример 13. Вызов аномимной функции.'''
<code>cubes
Пример 13 показывает вызов анонимной функции, возводящей в куб переданный параметр. Результатом выполнения этой инструкции будет бесконечный список кубов целых чисел, начиная с нуля. Необходимо отметить, что в
<code>add = \x -> \y -> x + y</code>
Остаётся отметить, что тип λ-абстракции определяется
== Инфиксный способ записи функций ==
Для некоторых функций возможен инфиксный способ записи
'''Пример 14. Инфиксная операция конкатенации списков.'''
<code>(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)</code>
'''Пример 15. Инфиксная операция композиции функций.'''
<code>(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)</code>
<code>(x ++) = \x -> (x ++ y)
(++ y) = \y -> (x ++ y)
(++) = \x y -> (x ++ y)</code>
Если
Для вновь определённых инфиксных операций возможно определение порядка вычисления. Для этого в
<code>infixr 5 ++
infixr 9 .</code>
В Хаскеле все функции являются нестрогими, что значит: все они поддерживают отложенные вычисления. Если какая-то функция определена как <code>bot = bot</code>, то её вызов даст ошибку, и такие ошибки обычно сложно отслеживать. Но если есть некая константная функция, которая определена как <code>constant_1 x = 1</code>, то при вызове конструкции <code>(constant_1 bot)</code> никакой ошибки не произойдёт, т.к. значение функции <code>bot</code> в этом случае не вычислялось бы (вычисления отложенные, значение вычисляется только тогда, когда оно действительно требуется). Результатом вычисления, естественно, будет <code>1</code>.
== Упражнения ==
1. Сконструировать следующие конечные списки (N — количество элементов в конструируемом списке). Для этого воспользоваться либо генераторами списков, либо конструирующими функциями.
*Список натуральных чисел. N = 20.
*Список нечётных натуральных чисел. N = 20.
*Список чётных натуральных чисел. N = 20.
*Список степеней двойки. N = 25.
*Список степеней тройки. N = 25.
*Список треугольных чисел [[w:Пьер Ферма|Ферма]]. N = 50.
*Список пирамидальных чисел Ферма. N = 50.
2. Сконструировать следующие бесконечные списки. Для этого воспользоваться либо генераторами списков, либо конструирующими функциями.
*Список факториалов.
*Список квадратов натуральных чисел.
*Список кубов натуральных чисел.
*Список степеней пятёрки.
*Список вторых суперстепеней натуральных чисел.
===
1. Конечные списки конструируются либо при помощи ограничений, вставляемых в генератор списка, либо при помощи дополнительных ограничивающих параметров.
*<code>[1 .. 20]</code>
*<code>[1, 3 .. 40]</code> или <code>[1, 3 .. 39]</code>
*<code>[2, 4 .. 40]</code>
<code>powerTwo 0 = []
powerTwo n = (2 ^ n) : powerTwo (n – 1)
reverse (powerTwo 25)</code>
*:Список степеней тройки проще всего сконструировать при помощи функции (здесь reverse — функция обращения списка):
<code>powerThree 0 = []
powerThree n = (2 ^ n) : powerThree (n – 1)
reverse (powerThree 25)</code>
*В отличие от предыдущих двух упражнений, здесь можно воспользоваться функцией map, применяющей заданную функцию ко всем элементам списка:
<code>t_Fermat 1 = 1
t_Fermat n = n + t_Fermat (n – 1)
map t_Fermat [1 .. 50]</code>
*Конструирование списка из 50 пирамидальных чисел Ферма также основывается на использовании функции <code>map</code>:
<code>p_Fermat 1 = 1
p_Fermat n = t_Fermat n + p_Fermat (n – 1)
map p_Fermat [1 .. 50]</code>
2.Бесконечные списки конструируются либо при помощи неограниченных генераторов, либо при помощи конструирующих функций без ограничивающих параметров.
*Бесконечный список факториалов:
<code>numbersFrom n = n : numbersFrom (n + 1)
factorial n = f_a n 1
Строка 218 ⟶ 196 :
f_a n m = f_a (n – 1) (n * m)
map factorial (numbersFrom 1)</code>
*Бесконечный список квадратов натуральных чисел:
<code>square n = n * n
map square (numbersFrom 1)</code>
*Бесконечный список кубов натуральных чисел:
<code>cube n = n ^ 3
map cube (numbersFrom 1)</code>
*Бесконечный список степеней пятёрки:
<code>powerFive n = 5 ^ n
map
*Бесконечный список вторых суперстепеней натуральных чисел:
<code>superPower n 0 = n
superPower n p = (superPower n (p – 1)) ^ n
secondSuperPower n = superPower n 2
map secondSuperPower (numbersFrom 1)</code>
|