#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 просто оставили это?