должен полностью описать, что вы можете с ним сделать. Если вы можете вызывать его только для определенных типов, это должно отражать сигнатура типа.
======================================================================================================================================================

тоящее время я пытаюсь обернуть голову вокруг типов классов и экземпляров, и пока не совсем понимаю их смысл. У меня есть два вопроса по этому вопросу:

1) Почему необходимо иметь класс типа в сигнатуре функции, когда функция использует некоторую функцию из этого класса типов. Пример:

Зачем ставить`(Eq a)` в подписи. Если`==` не определено для`a` тогда почему бы просто не скинуть ошибку при встрече`a == b`? Какой смысл в том, чтобы объявлять класс типа впереди?

2) Как связаны классы типов и перегрузка функций?

Это невозможно сделать:

Но это можно сделать так:

Что с этим? Почему у меня не может быть двух функций с одинаковым именем, но работающих на разных типах ... Исходя из C ++, я нахожу это очень странным. Но у меня, вероятно, неправильные представления о том, что это на самом деле. но как только я заверну их в эти экземпляры класса типов, я смогу.

Не стесняйтесь подбрасывать мне категории или вводить теоретические слова, так как я изучаю эти предметы параллельно с изучением Haskell, и я подозреваю, что в них есть теоретическая основа для того, как Haskell делает вещи здесь.

Я согласен с большей частью[Виллем Ван Онсем ответ](https://stackoverflow.com/a/47971470/465378), но я думаю, что он упускает из виду одно из главных преимуществ классов типов по сравнению с действительно специальной перегрузкой:**абстракция**, Представьте, что мы использовали специальную перегрузку вместо классов типов для определения`Monad` операции:

Теперь мы знаем, что каждая монада может быть выражена в терминах`pure` а также`>>=`как указано выше, но мытакже знать, что они могут быть эквивалентно выражены с помощью`fmap`, `pure`, а также`join`, Поэтому мы должны быть в состоянии реализовать`join` функция, которая работает наЛюбые монада:

Однако сейчас у нас проблема. Что такое`join`Тип?

Очевидно, что`join` должен быть полиморфным, так как он работает на любой монаде по замыслу. Но давая ему тип подписи`forall m a. m (m a) -> m a` было бы явно неправильно, так как этоне работают для всех типов, только монадические. Следовательно, нам нужно что-то в нашем типе, что выражает необходимость существования некоторой операции`(>>=) :: m a -> (a -> m b) -> m b`, что именно то, что обеспечивает ограничение класса типов.

Учитывая это, становится ясно, что специальная перегрузка позволяет перегрузить имена, но невозможно абстрагироваться от этих перегруженных имен, потому что нет гарантии, что различные реализации связаны каким-либо образом. Вымог определить монады без классов типов, но тогда вы не можете определить`join`, `when`, `unless`, `mapM`, `sequence`и все другие приятные вещи, которые вы получаете бесплатно, когда определяете только две операции.

Следовательно, классы типов необходимы в Haskell, чтобы обеспечить возможность повторного использования кода и избежать колоссального дублирования. Но не могли бы выобе перегрузка в стиле типа и специальная перегрузка имен?**да**и на самом деле Идрис делает. Но типичный вывод Идриса очень отличается от типичного в Хаскеле, поэтому его легче поддержать, чем в Хаскеле, по многим причинам в ответе Виллема.



Каждая переменная может использоваться как выражение самостоятельно;Каждое выражение имеет тип, которыйточно указывает, что вам разрешено делать с этим.Если у тебя есть

а также

затем, в соответствии с принципами, принятыми в Haskell,`f` все равно будет действительным выражением, само по себе, которое все еще должно иметьОдин тип. Хотя это можно сделать с использованием подтипов, это было гораздо сложнее, чем решение класса типов.

Точно так же, необходимость`Eq a` в

исходит из того, что тип`==` должен полностью описать, что вы можете с ним сделать. Если вы можете вызывать его только для определенных типов, это должно отражать сигнатура типа.



**потому что именно так был разработан Haskell**.

Зачем ставить`(Eq a)` в подписи. Если`==` тогда не определяется, почему бы просто не выдать ошибку при встрече`a == b`?

Почему мы помещаем типы в сигнатуру программы на C ++ (а не просто как утверждение в теле)? Потому что так устроен C ++. Как правило, концепция того, на чем строятся языки программирования:сделать явным то, что должно быть явным».

Не сказано, что модуль Haskell с открытым исходным кодом. Так что это означает, что у нас есть только подпись. Таким образом, это означает, что когда мы, например, пишем:

Мы часто пишем`foo` здесь с типами, которые не имеют`Eq` класс типов. В результате мы получили бы много ошибок, которые обнаруживаются только во время компиляции (или если бы Haskell был динамическим языком во время выполнения). Идея ставить`Eq a` в сигнатуре типа мы можем найти сигнатуру`foo` заранее, и, таким образом, убедитесь, что типы являются экземпляром класса типов.

Обратите внимание, что вам не нужно писать сигнатуры типов самостоятельно: Haskell обычно может получить сигнатуру функции, но сигнатура должна включать всю необходимую информацию для эффективного вызова и использования функции. Добавляя ограничения типов, мы ускоряем разработку.

Что с этим? Почему у меня не может быть двух функций с одинаковым именем, но работающих на разных типах.

Опять же: так устроен Haskell. Функции в функциональных языках программирования естьпервоклассные гражданеMsgstr "Это означает, что у них обычно есть имя, и мы хотим максимально избегать конфликтов имен. Точно так же, как классы в C ++ обычно имеют уникальное имя (за исключением пространств имен).

Скажем, вы бы определили две разные функции:

Тогда какой`incr` было бы`bar` должен выбрать? Конечно, мы можем сделать типы явными (т.е.`incr :: Bool -> Bool`), но обычно мы хотим избежать этой работы, так как она создает много шума.

Еще одна веская причина, по которой мы этого не делаем, заключается в том, что, как правило, класс типов - это не просто набор функций: он добавляет**контракты** к этим функциям. Например,`Monad` Класс типов должен удовлетворять определенным отношениям между функциями. Например`(>>= return)` должно быть эквивалентно`id`, Другими словами, класс типов:

Не описывает дванезависимый функции`(>>=)` а также`return`: это набор функций. У вас есть оба (обычно с некоторыми договорами между`>>=` а также`return`) или ничего из этого.



Тип подписи`f :: a -> a -> Bool` это сокращение для`f :: forall a. a -> a -> Bool`. `f` не будет действительно работатьдля всех типы`a` если это работает только для`a`с, которые имеют`(==)` определены. Это ограничение для типов, которые имеют`(==)` выражается с использованием ограничения`(Eq a)` в`f :: forall a. (Eq a) => a -> a -> Bool`.

«Для всех» / универсальная квантификация лежит в основе (параметрического) полиморфизма Хаскелла и, помимо прочего, обеспечивает мощные и важные свойства[parametricity](https://bartoszmilewski.com/2014/09/22/parametricity-money-for-nothing-and-theorems-for-free/).