Подтвердить что ты не робот

Haskell: ограничение равенства в случае

Я читал анонс ClassyPrelude и попал сюда:

instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a where
    filter = filterFunc

Затем автор сказал, что это не сработает:

instance (CanFilterFunc b a) => CanFilter (c -> c) a where
    filter = filterFunc

Что имеет смысл для меня, так как c полностью не связан с ограничением слева.

Однако, что не упоминается в статье, и что я не понимаю, почему это не сработает:

instance (CanFilterFunc b a) => CanFilter (b -> b) a where
    filter = filterFunc

Может кто-нибудь объяснить, почему это отличается от первого упомянутого определения? Возможно, полезный пример вывода типа GHC был бы полезен?

4b9b3361

Ответ 1

Майкл уже дает хорошее объяснение в своей статье в блоге, но я попытаюсь проиллюстрировать его с помощью (надуманного, но относительно небольшого) примера.

Нам нужны следующие расширения:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-}

Пусть определим класс, который проще, чем CanFilter, с одним параметром. Я определяю две копии класса, потому что хочу продемонстрировать разницу в поведении между двумя экземплярами:

class Twice1 f where
  twice1 :: f -> f

class Twice2 f where
  twice2 :: f -> f

Теперь давайте определим экземпляр для каждого класса. Для Twice1 мы фиксируем переменные типа одинаковыми напрямую, а для Twice2 мы позволяем им быть разными, но добавляем ограничение равенства.

instance Twice1 (a -> a) where
  twice1 f = f . f

instance (a ~ b) => Twice2 (a -> b) where
  twice2 f = f . f

Чтобы показать разницу, определим другую перегруженную функцию следующим образом:

class Example a where
  transform :: Int -> a

instance Example Int where
  transform n = n + 1

instance Example Char where
  transform _ = 'x'

Теперь мы находимся в точке, где мы можем видеть разницу. Определив

apply1 x = twice1 transform x
apply2 x = twice2 transform x

и спросите GHC о предполагаемых типах, получим, что

apply1 :: (Example a, Twice1 (Int -> a)) => Int -> a
apply2 :: Int -> Int

Почему? Ну, экземпляр для Twice1 срабатывает только тогда, когда источник и целевой тип функции одинаковы. Для transform и данного контекста мы этого не знаем. GHC будет применять экземпляр только после того, как правая сторона будет соответствовать, поэтому мы остаемся с неразрешенным контекстом. Если мы попробуем сказать apply1 0, появится ошибка типа, указывающая, что для устранения перегрузки все еще недостаточно информации. Мы должны явно указать тип результата как Int в этом случае, чтобы пройти.

Однако, в Twice2, экземпляр предназначен для любого типа функции. GHC немедленно ее устранит (GHC никогда не отступает, поэтому, если экземпляр явно соответствует, он всегда выбирается), а затем попытайтесь установить предварительные условия: в этом случае ограничение равенства, которое затем заставляет тип результата быть Int и позволяет также разрешить ограничение Example. Мы можем сказать apply2 0 без дополнительных аннотаций типа.

Итак, это довольно тонкая точка зрения о разрешении экземпляров GHC, и ограничение равенства здесь помогает проверке типа GHC таким образом, что пользователь требует меньше аннотаций типа.