14. Терминология:
Классы типов
Вышеупомянутое нужно лишь для того чтобы иметь
возможность писать полиморфные функции, в
которых мы абстрагируемся от конкретного типа
данных.
Пример: общая функция для объединения списка
элементов в один элемент.
15. Терминология:
Классы типов
Так как мы уже абстрагировались от всех
логических проблем, применение
объявленного функционала к конкретным
типам – проблема тривиальной компоновки.
17. Терминология:
Классы типов
Так выглядит полиморфная функция:
Обратите внимание, что в данном случае
моноид является не параметром функции, а её
условием.
Иными словами, данная функция требует
наличия нужного нам моноида. Т.е., в отличие
от наших примеров в Java, инстансы классов
типов передаются неявно (подразумеваются).
18. Терминология:
Запись (Record)
Композитный тип, позволяющий обращаться к
его составляющим по имени. Только и всего.
В случае Java это был бы POJO:
В случае Haskell это синтаксическое
расширение для декларации типов данных:
19. Как записи работают в Haskell
сейчас
Декларация типа данных с использованием
синтаксиса для записей просто
“рассахаривается” компилятором в объявления
одноимённых функций.
Таким образом, следующий код:
Превращается в
20. Как записи работают в Haskell
сейчас
Изменение значений записей производится с
использованием специального синтаксиса:
22. В чём проблема?
1. Конфликты имён
В силу того, что поля преобразуются в функции
и перегрузки функций в Haskell нет, нет и
возможности сосуществовать двум записям в
одном модуле, если они используют одни и те
же имена.
Данный код не будет компилироваться:
23. В чём проблема?
2. Частичность
Генерируемые функции определены не для
всех значений. Из-за этого, несмотря на то, что
следующий код пройдёт компиляцию без
ошибок, он выдаст ошибку только в рантайме:
24. В чём проблема?
3. Конфликты типов
Одно имя может быть использовано только
для одного типа данных.
Следующий код пройдёт компиляцию:
Этот - нет:
25. В чём проблема?
4. Избыточность конструктора
На практике записи крайне редко используются
для типов данных со множеством
конструкторов.
Набирание “Person” дважды в следующем коде
явно избыточно:
26. В чём проблема?
5. Изменение полей
Муторность изменения полей записей внутри
других записей растёт экспоненциально по
отношению к количеству уровней такого
вложения. Чем глубже мы пытаемся
обратиться – тем больше нам приходится
повторяться.
27. Линза (Lens)
Комбинируемая абстракция, которая решает
проблему изменения полей вложенных
записей.
● “over” - функция-комбинатор, позволяющая применять
функцию к значению поля, на которое ссылается линза
● “birthday” и “year” - линзы, вместе образующие единую
линзу при помощи комбинатора композиции,
обозначаемого точкой.
● “succ” - функция, прибавляющая единицу к числу
29. Линза (Lens)
Не решает остальных проблем системы
записей:
1. Конфликты имён
2. Частичность
3. Конфликты типов
4. Избыточность конструктора
5. Изменение полей
30. Решение:
Анонимные записи
В данный момент имплементированы как
препроцессор компилятора. Подробнее здесь:
http://hackage.haskell.org/package/record
31. Анонимные записи:
Декларации типов
● Отсутствие конфликтов имён. И “Person”, и
“Country” используют поле “name”.
● Нет нужды декларировать записи, что
используется в случае с полем “birthday”.
32. Анонимные записи:
Декларации типов
Несмотря на то, что анонимные записи являют
собой тип, а не декларацию, они по-прежнему
могут быть использованы для объявления
новых типов без каких-либо накладных
расходов.
Для этого нужно использовать конструкцию
“newtype”:
36. Анонимные записи:
Расход памяти
Следующий тип займёт меньше памяти
благодаря использованию оптимизации по
устранению промежуточных конструкторов во
вложенных типах.
К сожалению, к анонимным записям данная
оптимизация не применима. Стоит отметить,
однако, что “UNPACK” может уменьшать
производительность.
38. Анонимные записи:
Изменение значений полей
Никакого велосипеда. Просто используем
линзы.
Препроцессор предоставляет специальный
сахар для объявления линз, используя символ
“@”. Никаких издержек.
40. Анонимные записи:
Объявление значений
Для удобства есть ещё и синтаксис частичного
объявления, который генерирует функцию,
возвращающую значение.
Что, как понимают знающие, конечно же,
особенно полезно когда речь заходит об
аппликативных функторах:
41. Анонимные записи:
Все проблемы решены!
1. Конфликты имён
2. Частичность
3. Конфликты типов
4. Избыточность конструктора
5. Изменение полей
43. Анонимные записи:
Как они устроены?
Библиотека предоставляет набор
полиморфных типов данных, представляющих
собой строгие и ленивые записи с арностью до
24.
44. Анонимные записи:
Как они устроены?
Строчные значения на уровне типов
используются для обозначения имён полей.
Так как это значения, а понятие “пространство
имён” в принципе не применимо к значениям,
проблема конфликтов имён отпадает
автоматически.
45. Анонимные записи:
Как они устроены?
Декларация типа:
Преобразуется препроцессором в:
Обратите внимание на пересортировку полей
по алфавиту...
46. Анонимные записи:
Как они устроены?
Пересортировка полей позволяет добиться
следующего свойства:
Иными словами, имена полей предопределяют
запись, а позиции – нет.
47. Анонимные записи:
Как они устроены?
Класс типов используется для работы с
полями. Все предобъявленные в библиотеке
типы записей имеют инстансы этого класса.
Ниже представлена упрощённая версия
реализации.
53. Как пользоваться
Также возможно включать препроцессор
индивидуально для модуля. Для этого нужно
добавить следующую прагму в шапку модуля:
Конечно же, подразумевается, что Ваш проект
имеет те же зависимости, как и на
предыдущем слайде.
54. Как пользоваться
Оба способа использования подразумевают,
что директория бинарников, устанавливаемых
Cabal, (e.g., “.cabal/bin/”) упомянута в PATH.
55. Ссылки
● Библиотека типов анонимных записей:
http://hackage.haskell.org/package/record
● Препроцессор:
http://hackage.haskell.org/package/record-
preprocessor
● Мой блог с моими контактами:
http://nikita-volkov.github.io/