Перекрывающийся экземпляр для Show - PullRequest
1 голос
/ 02 мая 2019

Предположим, у нас есть следующее:

{-# LANGUAGE FlexibleInstances #-}

module Sample where

newtype A a =
  A a
  deriving (Show)

newtype L a =
  L [a]

class ListContainer l where
  getList :: l a -> [a]

instance ListContainer L where
  getList (L l) = l

instance (Show a, ListContainer l) => Show (l a) where
  show = const "example"

С этим кодом GHC жалуется:

warning: [-Wdeferred-type-errors]
• Overlapping instances for Show (A a)
    arising from a use of ‘GHC.Show.$dmshowList’
  Matching instances:
    instance (Show a, ListContainer l) => Show (l a)
      -- Defined at /.../src/Sample.hs:18:10
    instance Show a => Show (A a)
      -- Defined at /.../src/Sample.hs:7:13
• In the expression: GHC.Show.$dmshowList @(A a)
  In an equation for ‘showList’:
      showList = GHC.Show.$dmshowList @(A a)
  When typechecking the code for ‘showList’
    in a derived instance for ‘Show (A a)’:
    To see the code I am typechecking, use -ddump-deriv
  In the instance declaration for ‘Show (A a)’
warning: [-Wdeferred-type-errors]
• Overlapping instances for Show (A a)
    arising from a use of ‘GHC.Show.$dmshow’
  Matching instances:
    instance (Show a, ListContainer l) => Show (l a)
      -- Defined at /.../src/Sample.hs:18:10
    instance Show a => Show (A a)
      -- Defined at /.../src/Sample.hs:7:13
• In the expression: GHC.Show.$dmshow @(A a)
  In an equation for ‘show’: show = GHC.Show.$dmshow @(A a)
  When typechecking the code for ‘show’
    in a derived instance for ‘Show (A a)’:
    To see the code I am typechecking, use -ddump-deriv
  In the instance declaration for ‘Show (A a)’

Я могу понять, что он думает, что тип a может либо выводить Show, либо выводить ListContainer, что может привести к Show.

Как нам этого избежать?

Я понимаю, что существует функция showList, но ее подпись немного чужая. У меня уже есть функция, которую я собираюсь использовать для отображения определенных списков, которая возвращает String напрямую.

Ответы [ 2 ]

4 голосов
/ 02 мая 2019

Я могу понять, что он думает, что тип a может либо получить Show, либо получить ListContainer, что может привести к Show.

Это не то, чтоон думает.

Когда Haskell выбирает экземпляр класса, он вообще не смотрит на ограничения экземпляра.Все, что он учитывает при выборе экземпляра, это заголовок экземпляра (вещь, которая идет сразу после имени класса).

В вашем экземпляре Show заголовок экземпляра равен l a.Эта голова экземпляра соответствует A a (при условии l = A).Кстати, он также соответствует многим другим вещам - например, он соответствует Maybe a (где l = Maybe) и Either b al = Either b), Identity a и IO a - в значительной степеникаждый тип с параметром типа, если подумать.Неважно, что ни A, ни Maybe, ни IO не имеют экземпляра ListContainer, потому что, как я сказал выше, Haskell не смотрит на ограничения при выборе экземпляров, только на заголовки экземпляров.

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

Итак, возвращаясь к вашему примеру: теперь у A есть два совпадающих экземпляра Show - его собственный производный и один Show (l a).что вы написали, - компилятор жалуется, что они перекрываются.

1 голос
/ 02 мая 2019

В вашем примере вы можете просто удалить instance (Show a, ListContainer l) => Show (l a) и добавить deriving (Show) к L определению.

В качестве альтернативы вы можете удалить deriving (Show) из A определения.

Если вы хотите, чтобы ваш код вел себя так, как сейчас, удалите deriving (Show) и реализуйте его явно

 instance {-# OVERLAPPING #-}  Show a => Show (A a)
      where 
         show (A a) = "A " ++ show a
...