
- •Введение
- •1. Математика и современная информатика
- •2. Алгоритмы перевода высказываний с естественного языка на язык математики
- •3. Алгоритм, его свойства, типы и способы записи
- •4. Информация. Формы ее представления, виды и свойства
- •5. Информационные процессы
- •Классификация информационных процессов
- •Когнитивные информационные процессы
- •6. Аналоговая информация
- •7. Дискретная информация
- •8. Количество информации, единицы измерения информации
- •9. Высказывательные логические связки
- •Алгебраические и функциональные языки
- •Классификация функциональных языков
- •Определение атд
- •Синтаксически-ориентированное конструирование
- •Примеры описания атд
- •Атд в языке программирования Haskell
- •Общий вид определения атд в языке Haskell
- •Сопоставление с образцом
- •Классификация атд
- •Атд в других языках программирования
- •11. Системы счисления, используемые в вычислительной технике
- •12. Семантические категории. Логические структуры
- •13. Двоичное кодирование информации
- •Двоичное кодирование символьной (текстовой) информации
- •18. Теория информации и кодирования
- •19. Система кодирования
- •20. Импликация - логическая константа
- •25. Правило де Моргана
- •26. Двоичная система счисления. Операции в двоичной системе счисления
- •27. Восьмеричная система счисления. Операции в восьмеричной системе счисления
- •28. Кванторы общности и существования
- •29. Составные формулы
- •30. Порядок выполнения логических операций
- •Порядок выполнения логических операций в сложном логическом выражении:
- •31. Свойства логических операций
- •6. Законы поглощения:
- •7. Другие (1):
- •35. Закон исключения третьего
- •36. Кодирование изображений
- •37. Кодирование звуков
- •38. Классификационное кодирование
- •39. Кодирование текста
- •Способы кодирования информации.
- •Кодирование символьной (текстовой) информации.
- •Кодирование числовой информации.
- •Кодирование графической информации.
- •Кодирование звуковой информации.
Сопоставление с образцом
Представленные выше функции numerator и denominator показывают один важнейший механизм, реализованный в языке Haskell — сопоставление с образцами. Этот механизм используется в языке в нескольких аспектах, одним из главных является сопоставление с образцами при определении функций. Этот аспект необходимо рассмотреть более внимательно.
«Образцом» называется выражение, в котором некоторые или все поля декартова произведения (если декартово произведение не пусто, то есть конструктор не представляет собой простую метку размеченного объединения) заменены свободными переменными15 для подстановки конкретных значений таким образом, что при сопоставлении происходит означивание параметров образца — однозначное приписывание переменным образца конкретных значений. Для АТД образцом является указание одного из конструкторов с перечислением переменных или конкретных значений в качестве его полей.
Сопоставление с образцом проходит следующим образом. Из всех клозов функции16 выбирается первый по порядку, сопоставление со всеми образцами которого произошло успешно. Успешность заключается в корректном сопоставлении конкретных входных аргументов функции с соответствующими образцами. Сопоставление, как уже сказано, должно происходить однозначно и непротиворечиво. Константа сопоставляется только с такой же константой, свободной переменной в образце присваивается конкретное значение.
Этот процесс можно пояснить на примере. Пусть есть определение функции:
head :: [α] -> α
head [] = error "Empty list has no first element."
head (x:xs) = x
Здесь первая строка является сигнатурой, описывающей тип функции, вторая и третья — два клоза функции head соответственно. Сигнатура функции может не указываться в исходных кодах, так как строго типизированный язык Haskell имеет механизмы для однозначного вычисления типа любого объекта в наиболее общей форме (соответственно, наличие или отсутствие сигнатур функций не влияет на их работоспособность). Впрочем, многие разработчики говорят о том, что наличие сигнатур функций рядом с определениями позволяет более чётко понимать смысл и суть функции.
Первый клоз определяет значение функции в случае, если на вход функции подан пустой список. Как видно, функция предназначена для возвращения первого элемента («головы») списка, а потому для пустого списка её применение ошибочно. Используется системная функция error. Второй клоз применим для случаев, когда список не является пустым. Непустой список, как уже говорилось в теоретической части — это пара, первым элементом которой является некоторое значение, а вторым — список оставшихся элементов, «хвост» списка. В языке Haskell эти элементы декартова произведения соединяются при помощи конструктора (:), что и представлено в образце. Две свободные переменные — x и xs — это параметры образца, которые получают конкретные значения в случае корректного применения функции. Например:
> head [1, 2, 3]
сопоставит с переменной x конкретное значение 1, а с переменной xs — значение [2, 3]. Соответственно, результатом выполнения функции будет значение переменной x, то есть 1.
Приведённый пример можно понимать и по-другому. Раз список есть пара, созданная при помощи конструктора (:), то список [1, 2, 3] может быть представлен как (1:[2, 3]), а ещё вернее как (1:(2:(3:[]))). В данном случае вызов head [1, 2, 3] приведёт к следующей последовательности вычислений:
> let (x:xs) = (1:[2, 3])
in x
или, что то же самое:
> let x = 1
xs = [2, 3]
in x
В конечном итоге, поскольку свободная переменная xs не участвует в вычислении результата, оптимизирующий транслятор языка, основанного на ленивых вычислениях, проведёт такое преобразование17:
> let x = 1
in x
Итогом вычислений будет значение переменной x, то есть 1.
Теперь можно рассмотреть более сложный пример. Пусть определён АТД для представления бинарного дерева (такой же, как в теоретической части):
data Tree α = Empty
| Node α (Tree α) (Tree α)
Вот функция, которая вычисляет максимальную глубину заданного дерева. Уже по виду определения АТД можно сказать, что у неё должно быть не менее двух клозов, по одному на каждый конструктор АТД:
depth :: Tree α -> Int
depth Empty = 0
depth (Node _ l r) = 1 + max (depth l) (depth r)
Первый клоз функции определяет её значение для первого конструктора АТД Tree, то есть для пустого дерева. Второй клоз определяет значение функции уже для непустого дерева. У непустого дерева есть хранимый в узле элемент и два поддерева — левое и правое. Функции depth хранимый в узле элемент неинтересен, а потому в образце применена «маска подстановки» для неиспользуемых элементов АТД — (_). Этот символ при сопоставлении с образцами означает, что на его место может быть подставлено всё, что угодно. Кроме того, это единственный символ, который можно использовать несколько раз в одном образце. Два других компонента конструктора Node, l и r, сопоставляются с левым и правым поддеревьями соответственно.
Процесс сопоставления с образцом устроен таким образом, что не требует от значений АТД быть быть величинами, над которыми определена операция сравнения18 — возможность выбора конкретной части АТД обеспечивается наличием предикатов (необходимо вспомнить аксиому для предикатов АТД). Вместе с тем это накладывает дополнительные ограничения — нельзя написать определение функции, подобное следующему:
isElement x [] = False
isElement x (x:xs) = True
isElement x (y:xs) = isElement x xs
Здесь во втором клозе в образцах переменная x используется два раза, что недопустимо в образцах.
Таким образом, при сопоставлении с образцом происходит сравнение конструктора поданного на вход функции значения с конструктором в образце. Это значит, что технология сопоставления с образцом является очень мощной и гибкой при определении функций — она не требует явного определения функций сравнения величин.