Объясните классы типа в Haskell

Я - C++ / программист Java и основная парадигма, которую я, оказывается, использую в повседневном программировании, ООП. В некотором потоке я прочитал комментарий, что классы Типа более интуитивны по своей природе, чем ООП. Кто-то может объяснить понятие классов типа в простых словах так, чтобы парень ООП как я мог понять это?

9
задан Tsubasa Gomamoto 21 April 2010 в 18:42
поделиться

5 ответов

Во-первых, я всегда очень подозрительно отношусь к утверждениям о том, что та или иная структура программы более интуитивно понятна. Программирование противоречит интуиции и всегда будет таким, потому что люди, естественно, думают в терминах конкретных случаев, а не общих правил. Чтобы изменить это, требуется обучение и практика, также известная как «обучение программированию».

Переходя к сути вопроса, ключевое различие между объектно-ориентированными классами и классами типов Haskell состоит в том, что в объектно-ориентированном стиле класс (даже класс интерфейса) является одновременно типом и шаблоном для новых типов (потомков). В Haskell класс типов - это только шаблон для новых типов. Точнее, класс типов описывает набор типов, которые имеют общий интерфейс, но сам не является типом .

Итак, класс типов «Num» описывает числовые типы с операторами сложения, вычитания и умножения. Тип «Integer» является экземпляром «Num», что означает, что Integer является членом набора типов, реализующих эти операторы.

Итак, я могу написать функцию суммирования с таким типом:

sum :: Num a => [a] -> a

Бит слева от оператора «=>» говорит, что «сумма» будет работать для любого типа «а», который является экземпляром Num. Бит справа говорит, что он принимает список значений типа «a» и в результате возвращает одно значение типа «a». Таким образом, вы можете использовать его для суммирования списка целых чисел или списка двойных чисел или списка сложных, потому что все они являются экземплярами «Num».Реализация «sum», конечно же, будет использовать оператор «+», поэтому вам нужно ограничение типа «Num».

Однако вы не можете написать это:

sum :: [Num] -> Num

, потому что «Num» не является типом.

Это различие между типом и классом типов является причиной того, что мы не говорим о наследовании и потомках типов в Haskell. Это своего рода наследование для классов типов: вы можете объявить один класс типов как потомок другого. Потомок здесь описывает подмножество типов, описываемых родителем.

Важным следствием всего этого является то, что в Haskell не может быть разнородных списков. В примере с «суммой» вы можете передать ему список целых чисел или список двойных чисел, но вы не можете смешивать двойные и целые числа в одном списке. Это похоже на хитрое ограничение; как бы вы реализовали старый пример «автомобили и грузовики - это оба типа транспортных средств»? Есть несколько ответов в зависимости от проблемы, которую вы на самом деле пытаетесь решить, но общий принцип заключается в том, что вы делаете косвенное обращение явно, используя первоклассные функции, а не неявно используя виртуальные функции.

26
ответ дан 4 December 2019 в 05:54
поделиться

В C ++ и т. Д. "Виртуальные методы" отправляются в соответствии с типом this / self неявный аргумент. (Метод указан в таблице функций, на которую неявно указывает объект)

Классы типов работают по-другому и могут делать все, что могут «интерфейсы», и даже больше. Начнем с простого примера того, чего не могут сделать интерфейсы: класс типа Read Haskell.

ghci> -- this is a Haskell comment, like using "//" in C++
ghci> -- and ghci is an interactive Haskell shell
ghci> 3 + read "5" -- Haskell syntax is different, in C: 3 + read("5")
8
ghci> sum (read "[3, 5]") -- [3, 5] is a list containing 3 and 5
8
ghci> -- let us find out the type of "read"
ghci> :t read
read :: (Read a) => String -> a

read имеет тип (Read a) => String -> a , что означает, что для каждого типа, реализующего тип-класс Read , read может преобразовать String в этот тип. Это отправка на основе возвращаемого типа, невозможная с "интерфейсами".

Это невозможно сделать в подходе C ++ и др., Где таблица функций извлекается из объекта - здесь у вас даже нет соответствующего объекта до тех пор, пока read не вернет его, так как можно вы называете это?

Ключевое отличие реализации от интерфейсов, которое позволяет этому случиться, состоит в том, что таблица функций не указывается внутри объекта, она передается компилятором отдельно вызываемым функциям.

Кроме того, в C ++ / etc, когда кто-то определяет класс, они также несут ответственность за реализацию своих интерфейсов. Это означает, что вы не можете просто изобрести новый интерфейс и реализовать его в Int или std :: vector .

В Haskell это можно сделать, и это не с помощью «обезьяньего патча», как в Ruby. В Haskell есть хорошая схема разделения имен, что означает, что два класса типов могут иметь функцию с одним и тем же именем, а тип может реализовывать обе.

Это позволяет Haskell иметь много простых классов, таких как Eq (типы, поддерживающие проверку равенства), Show (типы, которые могут быть напечатаны в String ) , Прочтите (типы, которые могут быть проанализированы из String ), Monoid (типы, которые имеют операцию конкатенации и пустой элемент) и многие другие, и позволяет даже для примитивных типов, таких как Int , для реализации соответствующих классов типов.

Из-за разнообразия классов типов люди склонны программировать более общие типы, а затем имеют больше функций многократного использования, а поскольку у них также меньше свободы, когда типы являются общими, они могут даже создавать меньше ошибок!

tldr: type-classes == awesome

10
ответ дан 4 December 2019 в 05:54
поделиться

Класс типа можно сравнить с концепцией «реализации» интерфейса. Если какой-либо тип данных в Haskell реализует интерфейс «Показать», его можно использовать со всеми функциями, которые ожидают объект «Показать».

3
ответ дан 4 December 2019 в 05:54
поделиться

Краткая версия: Классы типов - это то, что Haskell использует для специального полиморфизма.

... но это, вероятно, ничего вам не прояснило.

Полиморфизм должен быть знаком людям с опытом работы с ООП. Однако ключевым моментом здесь является разница между параметрическим и специальным полиморфизмом.

Параметрический полиморфизм означает функции, которые работают со структурным типом, который сам параметризуется другими типами, например списком значений. Параметрический полиморфизм является нормой повсюду в Haskell; C # и Java называют это «дженериками» . По сути, универсальная функция делает то же самое с конкретной структурой, независимо от параметров типа.

Специальный полиморфизм , с другой стороны, означает набор различных функций, выполняющих разные (но концептуально связанные) вещи в зависимости от типов. В отличие от параметрического полиморфизма, специальные полиморфные функции необходимо указывать индивидуально для каждого возможного типа, с которым они могут использоваться. Специальный полиморфизм, таким образом, является обобщенным термином для множества функций, имеющихся в других языках, таких как перегрузка функций в C / C ++ или полиморфизм диспетчеризации на основе классов в ООП.

Основным преимуществом классов типов Haskell по сравнению с другими формами специального полиморфизма является большая гибкость, поскольку разрешает полиморфизм в любом месте сигнатуры типа . Например, большинство языков не распознают перегруженные функции по типу возвращаемого значения; Типовые классы могут.

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

Некоторые компиляторы Haskell, в том числе самый популярный, GHC, предлагают языковые расширения, которые делают классы типов еще более мощными, например классы многопараметрических типов , которые позволяют выполнять специальные полиморфные функции. отправка на основе нескольких типов (аналогично тому, что называется «множественная отправка» в ООП).


Чтобы попытаться дать вам немного его вкуса, вот некоторый неопределенно приправленный Java / C # псевдокод:

interface IApplicative<>
{
    IApplicative<T> Pure<T>(T item);
    IApplicative<U> Map<T, U>(Function<T, U> mapFunc, IApplicative<T> source);
    IApplicative<U> Apply<T, U>(IApplicative<Function<T, U>> apFunc, IApplicative<T> source);
}

interface IReducible<>
{
    U Reduce<T,U>(Function<T, U, U> reduceFunc, U seed, IReducible<T> source);
}

Обратите внимание, что мы, среди прочего, определяем интерфейс над универсальным типом и определение метода, в котором тип интерфейса отображается только как возвращаемый тип , Pure . Не очевидно, что каждое использование имени интерфейса должно означать один и тот же тип (т.е. не смешивать разные типы, реализующие интерфейс), но я не знал, как это выразить.

13
ответ дан 4 December 2019 в 05:54
поделиться

В дополнение к тому, что xtofl и camccann уже написали в своих превосходных ответах, при сравнении интерфейсов Java с классами типов Haskell полезно заметить следующее:

  1. Интерфейсы Java закрыты , что означает, что набор интерфейсов, реализуемых любым данным классом, определяется раз и навсегда, когда и где он определен;

  2. Классы типов Haskell открыты ], что означает, что любой тип (или группа типов для классов с несколькими параметрами) может быть сделан членом любого класса типов в любое время, при условии, что подходящие определения могут быть предоставлены для функций, определенных классом типа.

Эта открытость классов типов (и протоколов Clojure, которые очень похожи) является очень полезным свойством; программист на Haskell обычно придумывает новую абстракцию и сразу же применяет ее к ряду проблем, связанных с уже существующими типами, с помощью умного использования классов типов.

7
ответ дан 4 December 2019 в 05:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: