Почему в Haskell мне нужно указывать класс типов в объявлении экземпляра?

#haskell

#haskell

Вопрос:

Следующее не компилируется:

 data Point a b = Point { x :: a
                       , y :: b
                       } deriving (Show)

instance Eq (Point a b) where
  (Point x _) == (Point y _) = x == y
  

Ошибка:

 No instance for (Eq a)
  arising from a use of `=='
In the expression: x == y
In an equation for `==': (Point x _) == (Point y _) = x == y
In the instance declaration for `Eq (Point a b)'
  

Однако, если я добавлю класс типов в экземпляр, тогда это сработает:

 data Point a b = Point { x :: a
                       , y :: b
                       } deriving (Show)

instance (Eq a) => Eq (Point a b) where
  (Point x _) == (Point y _) = x == y
  

Разве компилятор не может увидеть, что я использую a == a там, и вывести, что a должно быть в классе типов Eq ?

Комментарии:

1. Ваше Eq объявление экземпляра выглядит подозрительным для меня. Помимо того, что ваш код говорит компилятору , он говорит миру , что второй компонент точки не имеет значения , за исключением, возможно, для какой-то внутренней оптимизации. Ваш Show экземпляр, с другой стороны, говорит об обратном, что оба компонента имеют значение.

2. Если второй компонент действительно не имеет значения, то вам не следует выводить Show , а вместо этого следует сделать что-то смутно похожее instance (Show a, Show b) => Show (Point a b) where showsPrec _ (Point x y) s = "Point " shows x , а затем, для отладки, написать свою собственную функцию showDirtyLaundry (Point a b) = "Point " show a show b . Если, однако, второй компонент имеет значение для остального мира, вы должны вместо этого использовать deriving (Eq, Show) и сделать все более понятным. В некоторых случаях использование может также повысить эффективность deriving , поэтому используйте его всякий раз, когда это уместно.

Ответ №1:

Это может быть двойка, которая a должна быть в классе типов Eq . Именно поэтому он жалуется. Вы объявили instance Eq (Point a b) , в котором говорится, что типы формы Point a b находятся в Eq классе типов для любых типов a и b , но вы дали определение == , которое работает только тогда, когда a является членом Eq .

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

Комментарии:

1. Есть ли какой-либо способ для меня написать приведенный выше код без записи Eq a ?

2. @Eyal Нет, если вы не хотите, чтобы ваш эквалайзер был более стандартным (т. Е. Используйте запись y), и в этом случае вы могли бы получить Eq

3. @Eyal: Нет. Зачем вам это нужно?

4. @Eyal: Если бы это было разрешено, было бы сложнее определить, какие ограничения вам нужны для аргументов, заданных методу класса типа для данного экземпляра. Если вам требуется поместить ограничения в заголовок экземпляра, все, что вам нужно сделать, это посмотреть на первую строку определения экземпляра. Если бы вы не были обязательны, вы бы в значительной степени лгали о фактическом типе в заголовке экземпляра, потому что что-то вроде Point a b (без ограничений) подразумевает, что это работает для всех типов a и b в Haskell.

5. @alternative: производный экземпляр Eq for Point a b будет иметь ограничения Eq для обоих a и b , поэтому вы на самом деле не избегаете этого.

Ответ №2:

Представьте себе функцию

 equals ::  a -> a -> Bool
equals = (==)
  

Компилятор, очевидно, может сделать вывод, что тип equals должен быть Eq a => a -> a -> Bool . Но это ошибка, потому что вы объявили, что ваш тип работает для всех a .

Экземпляры класса типов похожи, за исключением того, что у нас нет возможности разрешить их вывод таким же образом, для которого мы могли бы оставить объявление типа equals . Поэтому необходимо указать ограничение таким же образом, что если вы указываете сигнатуру типа функции, вы также должны указать ограничение.

То есть ghc не будет выводить только ограничение; он должен либо выводить всю сигнатуру, либо ничего из нее. (Ну, это выводит в любом случае, но вывод должен быть более общим, чем то, как вы его ввели, если вы его ввели — и ограничения подпадают под это требование).

Подумайте о своем начальном instance Eq (Point a b) as (==) :: (Point a b) -> (Point a b) -> Bool , который невозможно четко определить так, как вам нравится, поскольку предоставление ему этого тела будет иметь (==) :: Eq a => (Point a b) -> (Point a b) -> Bool .

Комментарии:

1. Вы пишете, что экземпляры класса типов не имеют возможности разрешать им выводиться как функция do . Почему нет? Есть ли что-то в лямбда-исчислении или системах алгебраических типов, что делает это невозможным? Или разработчики Haskell просто оставили это?