2012-07-19 2 views
31

я читал через объявления ClassyPrelude и получил здесь:Haskell: ограничение равенства в случае

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

Писатель тогда отметил, что это не будет работать:

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

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

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

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

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

ответ

49

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

Нам понадобятся следующие расширения:

{-# 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 таким образом, чтобы пользовательский запрос требовал меньше аннотаций типа.

+0

Спасибо. Я подозревал, что это что-то вроде этого, но ваш упрощенный пример сделал многое более ясным. – Clinton

Смежные вопросы