Перемещение вызова функции туда, где предложение нарушает проверку типов с помощью OverlappingInstances.

Я использовал OverlappingInstances сделать красивый класс печати, который по умолчанию Show всякий раз, когда я не предоставил пользовательский экземпляр для какого-либо типа.

По некоторым причинам это, кажется, ломается всякий раз, когда вы используете where пункт или let выражение.

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

class View a where
    view :: a -> String

instance {-# OVERLAPS #-} Show a => View a where
    view = show

-- Works just fine
instance (View a, View b) => View (a, b) where
    view (a, b) = "(" ++ view a ++ ", " ++ view b ++ ")"

-- Does not work
instance (View a, View b) => View (a, b) where
    view (a, b) = "(" ++ a' ++ ", " ++ b' ++ ")"
      where
        a' = view a
        b' = view b

-- Does not work
instance (View a, View b) => View (a, b) where
    view (a, b) = let
        a' = view a
        b' = view b
        in "(" ++ a' ++ ", " ++ b' ++ ")"

Теперь, если я удаляю перекрывающийся экземпляр по умолчанию, все остальные экземпляры работают нормально.

Я надеюсь, что кто-то может объяснить мне, почему это происходит, или это просто ошибка?

Конкретные ошибки, которые я получаю для каждой из них:

Could not deduce (Show a) arising from a use of ‘view’ from the context (View a, View b) bound by the instance declaration at ...
Could not deduce (Show b) arising from a use of ‘view’ from the context (View a, View b) bound by the instance declaration at ...

Так по какой-то причине where / let обманывают проверку типа в мышление View требует Showкогда это не так.

2 ответа

Решение

Спасибо @dfeuer за альтернативный подход, но я решил написать довольно быстрое решение для самого вопроса:

{-# LANGUAGE MonoLocalBinds #-}

Как только это помещено наверх файла, все работает просто отлично. Из исследования, которое я сделал, кажется, что некоторые расширения (я думаю, в том числе OverlappingInstances) просовывать дыры в локальной проверке типов привязки и MonoLocalBinds жертвует, используя местные связи полиморфно, чтобы исправить те дыры.

Подход семейства типов выглядит следующим образом:

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses,
    FlexibleInstances, DataKinds, KindSignatures,
    ScopedTypeVariables #-}

import Data.Proxy


data Name = Default | Booly | Inty | Pairy Name Name

type family ViewF (a :: *) :: Name where
  ViewF Bool = 'Booly
  ViewF Int = 'Inty
  ViewF Integer = 'Inty --you can use one instance many times
  ViewF (a, b) = 'Pairy (ViewF a) (ViewF b)
  ViewF a = 'Default

class View (name :: Name) a where
  view' :: proxy name -> a -> String

instance (Show a, Num a) => View 'Inty a where
  view' _ x = "Looks Inty: " ++ show (x + 3)

instance a ~ Bool => View 'Booly a where
  view' _ x = "Looks Booly: " ++ show (not x)

instance Show a => View 'Default a where
  view' _ x = "Looks fishy: " ++ show x

instance (View n1 x1, View n2 x2) => View ('Pairy n1 n2) (x1, x2) where
  view' _ (x, y) = view' (Proxy :: Proxy n1) x ++ "," ++ view' (Proxy :: Proxy n2) y


view :: forall a name .
        (ViewF a ~ name, View name a)
     => a -> String
view x = view' (Proxy :: Proxy name) x

-- Example:

hello :: String
hello = "(" ++ view True ++ view (3 :: Int)
        ++ view "hi" ++ ")"
Другие вопросы по тегам