Основы функционального программирования/Основы языка Haskell: различия между версиями
Содержимое удалено Содержимое добавлено
Ramir (обсуждение | вклад) Нет описания правки |
Нет описания правки |
||
Строка 1:
{{ОФП Содержание}}
Настоящая лекция будет полностью посвящена синтаксису языка [[w:Haskell|Haskell]] (далее, для удобства, «
▲Настоящая лекция будет полностью посвящена синтаксису языка [[w:Haskell|Haskell]] (далее, для удобства, «Хаскель»). Будут рассмотрены все важнейшие понятия языка, их соотношение с уже изученными понятиями (на основе абстрактного функционального языка). Также по возможности будут приводиться примеры на [[w:Лисп|Лиспе]], чтобы не отрываться от основ и традиции.
== Структуры данных и их типы ==
Одна из основных единиц любого языка программирования — это символ. Символом традиционно называется последовательность букв, цифр и специальных знаков ограниченной или неограниченной длины. В некоторых языках строчные и прописные буквы различаются (к ним относится
Символы чаще всего являются идентификаторами — именами констант, переменных, функций. Значениями же констант, переменных и функций являются типизированные последовательности знаков. Пример: значением числовой константы не может быть строка из букв. В функциональных языках есть понятие '''атом'''. В реализациях атомами называются символы и
[[w:Список (структура данных)|Список]]
Списочные структуры в Лиспе и Хаскеле описываются в соответствии с нотацией: заключением одного списка в другой. При этом в нотации Лиспа сделано послабление, т. к. перед скобкой внутреннего списка можно не ставить пробел.
Как говорилось во '''[[Основы функционального программирования/Вводная лекция|вводной лекции]]''', типы данных в функциональных языках определяются автоматически. Механизм автоматического определения встроен и в
<code>5 :: Integer</code>
Это будет читаться как «Числовая константа <code>5</code> имеет тип Целое число».
Ещё
== Соглашения по именованию ==
Строка 29 ⟶ 27 :
== Определители списков и математические последовательности ==
Пожалуй,
<code>[ x | x <- xs ]</code>
Это можно прочесть как «Список из всех таких <code>x</code>, взятых из <code>xs</code>». Структура «<code>x
<code>[ x | x <- xs, x > m, x < n ]</code>
можно прочесть «Список из всех таких <code>x</code>, взятых из <code>xs</code>, что <code>x</code> больше <code>m</code> '''И''' <code>x</code> меньше <code>n</code>».
В Хаскеле можно формировать '''бесконечные''' списки и структуры данных. Бесконечные списки можно формировать на основе определителей списков или c помощью специальной нотации. Вот, например, бесконечный список, состоящий из последовательности [[w:натуральное число|натуральных чисел]]: <code>[1, 2 ..]</code>. Бесконечная последовательность [[w:чётность|нечётных]] натуральных чисел: <code>[1, 3 ..]</code>.
При помощи двух точек можно также определять любую [[w:арифметическая прогрессия|арифметическую прогрессию]], конечную или [[w:бесконечность|бесконечную]]. Для конечной задаются первый и последний элементы. Разность арифметической прогрессии вычисляется на основе первого и второго заданного элементов — в
Бесконечные структуры данных можно определять на основе бесконечных списков, а можно использовать механизм [[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>
Строка 55 ⟶ 53 :
В этом примере показан способ определения бесконечного типа. Видно, что без рекурсии тут не обошлось. Если нет необходимости создавать новый тип данных, бесконечную структуру можно получить через функции:
<code>ones = 1 : ones
numbersFrom n = n : numberFrom (n + 1)
squares = map (^2) (numbersFrom 0)</code>
Первая функция определяет бесконечную последовательность, полностью состоящую из [[w:1 (число)|единиц]]. Вторая функция возвращает последовательность целых чисел, начиная с заданного. Третья возвращает бесконечную последовательность квадратов натуральных чисел вместе с
== Вызовы функций ==
Математическая нотация вызова функции традиционно полагала заключение параметров вызова в скобки. Эту традицию впоследствии переняли практически все императивные языки. В функциональных языках принята иная нотация: имя функции отделяется от её параметров просто пробелом. В Лиспе вызов функции <code>length</code> с неким параметром <code>L</code> записывается в виде списка: <code>(length L)</code>. Такая нотация объясняется тем, что большинство функций в функциональных языках каррированны.
В Хаскеле нет нужды обрамлять вызов функции в виде списка. Например, если определена функция, складывающая два числа:
<code>add :: Integer -> Integer -> Integer
add x y = x + y</code>
То
<code>add 5 7</code>
Здесь видно, что нотация
<code>add (x, y) = x + y</code>
Можно видеть, что последняя запись — это функция с одним аргументом в строгой
<code>inc :: Integer -> Integer
Т. е. в этом случае вызов функции <code>inc</code> с одним параметром просто
<code>map
map f [] = []
map f (x:xs) = (f x) : (map f xs)</code>
Как видно, здесь использована инфиксная запись операции <code>prefix</code> — двоеточие, только такая запись используется в нотации
<code>map (add 1) [1, 2, 3, 4]</code>
Строка 96 ⟶ 95 :
== Использование λ-исчисления ==
Функциональная парадигма программирования основана на [[w:λ-исчисление|λ-исчислении]], поэтому вполне закономерно, что все функциональные языки поддерживают нотацию для описания λ-абстракций.
'''Пример 12. Функции add и inc, определённые через λ-абстракции.'''▼
<!-- Неплохо бы для примеров сделать заголовки. Пока отложу -->
<code>add = \x y -> x + y
inc = \x -> x + 1</code>
'''Пример 13. Вызов аномимной функции
<code>cubes = map (\x -> x * x * x) [0 ..]</code>
Пример 13 показывает вызов анонимной функции, возводящей в куб переданный параметр. Результатом выполнения этой инструкции будет бесконечный список кубов целых чисел, начиная с нуля. Необходимо отметить, что в Хаскеле используется
<code>add = \x -> \y -> x + y</code>
Остаётся отметить, что тип λ-абстракции определяется точно так же, как и тип функций. Тип λ-выражения вида <
== Инфиксный способ записи функций ==
Строка 117:
Для некоторых функций возможен инфиксный способ записи. Это обычно простые двоичные операции. Вот как, например, определены операции конкатенации списков и композиции функций:
'''Пример 14. Инфиксная операция конкатенации списков
<code>(++) :: [a] -> [a] -> [a]
Строка 123:
(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)
Строка 136:
Это три секции, каждая из которых определяет инфиксную операцию конкатенации списков в соответствии с количеством переданных ей аргументов. Использование круглых скобок в записи секций обязательно.
Если функция принимает два параметра, то её также можно записывать в инфиксной форме. Однако если просто записать между параметрами имя функции, это будет ошибкой, ибо в строгой нотации
Для вновь определённых инфиксных операций возможно определение порядка вычисления. Для этого в Хаскеле есть зарезервированное слово <code>infixr</code>, которое назначает операции приоритет выполения в интервале от 0 до 9: 9 объявляется самой сильной степенью значимости. 10 также входит в этот интервал, и именно эту степень имеет операция применения). Вот так определяются степени для определенных в примерах 14 и 15 операций:
Строка 143:
infixr 9 .</code>
В Хаскеле все функции являются нестрогими, что значит: все они поддерживают отложенные вычисления. Если какая-то функция определена как <code>bot = bot</code>, то её вызов даст ошибку, и такие ошибки обычно сложно отслеживать. Но если есть некая константная функция, которая определена как <code>constant_1 x = 1</code>, то при вызове конструкции <code>(constant_1 bot)</code> никакой ошибки не произойдёт, т. к. значение функции <code>bot</code> в этом случае не вычислялось бы (вычисления отложенные, значение вычисляется только тогда, когда оно действительно требуется). Результатом вычисления, естественно, будет <code>1</code>.
== Упражнения ==
#*Список натуральных чисел. N = 20.
#*Список нечётных натуральных чисел. N = 20.
#*Список чётных натуральных чисел. N = 20.
#*Список степеней двойки. N = 25.
#*Список степеней тройки. N = 25.
#*Список треугольных чисел [[w:Пьер Ферма|Ферма]]. N = 50.
#*Список пирамидальных чисел Ферма. N = 50.
#*Список факториалов.▼
#*Список квадратов натуральных чисел.▼
#*Список кубов натуральных чисел.▼
#*Список степеней пятёрки.▼
#*Список вторых суперстепеней натуральных чисел.▼
▲2. Сконструировать следующие бесконечные списки. Для этого воспользоваться либо генераторами списков, либо конструирующими функциями.
▲*Список факториалов.
▲*Список квадратов натуральных чисел.
▲*Список кубов натуральных чисел.
▲*Список степеней пятёрки.
▲*Список вторых суперстепеней натуральных чисел.
#*<code>[1 .. 20]</code>▼
▲=== Ответы ===
#*<code>[1, 3 .. 40]</code> или <code>[1, 3 .. 39]</code>▼
#*<code>[2, 4 .. 40]</code>▼
▲1. Конечные списки конструируются либо при помощи ограничений, вставляемых в генератор списка, либо при помощи дополнительных ограничивающих параметров.
#*Список степеней двойки проще всего сконструировать при помощи функции (здесь <code>reverse</code> — функция обращения списка):▼
▲*<code>[1 .. 20]</code>
▲*<code>[1, 3 .. 40]</code> или <code>[1, 3 .. 39]</code>
▲*<code>[2, 4 .. 40]</code>
▲*Список степеней двойки проще всего сконструировать при помощи функции (здесь <code>reverse</code> — функция обращения списка):
<code>powerTwo 0 = []
powerTwo n = (2 ^ n) : powerTwo (n – 1)
reverse (powerTwo 25)</code>
#*
<code>powerThree 0 = []
powerThree n = (2 ^ n) : powerThree (n – 1)
reverse (powerThree 25)</code>
#*В отличие от предыдущих двух упражнений, здесь можно воспользоваться функцией <code>map</code>, применяющей заданную функцию ко всем элементам списка:
<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
f_a 1 m = m
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 powerFive (numbersFrom 1)</code>
#*Бесконечный список вторых суперстепеней натуральных чисел:
<code>superPower n 0 = n
superPower n p = (superPower n (p – 1)) ^ n
secondSuperPower n = superPower n 2
map secondSuperPower (numbersFrom 1)</code>
|