Раздел 21 · Haskell

Типоклассы: класс, инстанс, deriving и законы

middle~45 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Типоклассы: класс, инстанс, deriving и законы

Типокласс это интерфейс с законами, главный механизм полиморфизма в Haskell. В этом уроке: объявляем класс, пишем инстансы руками, выводим механическое через deriving и разбираем, что значит словарь под капотом. Берём базовую обойму Eq, Ord, Show, Enum, Bounded и иерархию Num, а Read используем как анти-пример: почему в проде парсят иначе.

Идея

Урок 06 закончился неловкостью. Типы у нас появились, но они немые: напечатать Status пришлось выпрашивать заклинанием deriving Show, сравнить два значения через == нельзя, отсортировать список заказов нечем. И при этом == прекрасно работает на Int, show печатает и числа, и строки, и списки, а + складывает и Int, и Double. Одно имя, разное поведение, выбор по типу. Как так?

Ответ это типоклассы, и пользовался ты ими с первого урока, только не знал имени. ==, compare, show, +, < это не встроенные операторы, а методы классов. Класс это интерфейс: список методов, которые тип обязан предоставить, чтобы войти в клуб. Конкретная реализация под конкретный тип это инстанс. Int входит в класс Eq, поэтому == на нём работает. Твой Status в Eq пока не входит, поэтому не работает.

Это ad-hoc полиморфизм: разный код под одним именем, выбранный по типу. Не путай с параметрическим полиморфизмом из урока 04, где length :: [a] -> Int это один и тот же код для любого a. Тут наоборот: для каждого типа своя реализация, а имя общее.

Если знаешь другие языки, держи карту соответствий, но читай оговорки:

  • Интерфейс в TS или Java ближе всего по интуиции, но устроен иначе. Там метод живёт внутри объекта, и диспетчеризация идёт по значению в рантайме (таблица методов спрятана в самом объекте). В Haskell диспетчеризация идёт по типу на этапе компиляции, а сам тип в рантайме уже стёрт.
  • Трейт в Rust это прямой потомок типоклассов: Rust взял идею почти дословно, trait это class, impl это instance, trait bounds это ограничения в сигнатуре. Если проходил Rust-раздел, узнаёшь всё до запятой.

Два отличия от ООП-интерфейса стоит назвать сразу, потому что они меняют, как ты думаешь. Первое: инстанс можно добавить после объявления типа, и даже для чужого типа из библиотеки, не трогая его исходники. Интерфейс не нужно предусматривать заранее. Второе: бывает полиморфизм по возвращаемому типу. minBound :: Int выбирает реализацию по типу результата, хотя аргументов нет вообще. В мире, где метод привязан к объекту-аргументу, такое невыразимо. Почему в Haskell выразимо, станет ясно, как только мы заглянем под капот.

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.