Как установить ограничения на связанные данные?

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

class (Context (Associated a b)) => Class a where
  data Associated a :: * -> *

instance Context (Associated a b) where
  func1 = error "func1"

Тем не менее, свободная переменная b, которая не входит в сферу охвата, мешает мне. Одним из решений является копирование функций класса из Context, но оно выглядит уродливым.

class Class a where
  data Associated a :: * -> *
  -- duplicate all functions from class Context
  contextFunc1 :: Associated a b -> String

instance Class a => Context (Associated a b) where
  func1 = contextFunc1

Есть ли идиоматический способ установить ограничения на связанный тип данных, который имеет переменные, не упомянутые в голове?

edit: Я хотел бы поддерживать совместимость с GHC 7.0.3

Ответы

Ответ 1

У меня нет GHC 7.0.3, но я думаю, что это должно с ним работать.

Вы можете передать словари вручную, как это (используя Context= Show в качестве примера):

{-# LANGUAGE ScopedTypeVariables, TypeFamilies, ExistentialQuantification #-}

data ShowDict a = Show a => ShowDict

class Class a where
  data Associated a :: * -> *

  getShow :: ShowDict (Associated a b)

-- Convenience function
getShowFor :: Class a => Associated a b -> ShowDict (Associated a b)
getShowFor _ = getShow

showAssociated :: Class a => Associated a b -> String
showAssociated a = 
  case getShowFor a of
    ShowDict -> -- Show (Associated a b) is made available by this pattern match 
      show a

instance Class Int where
  data Associated Int b = Foo deriving Show

  getShow = ShowDict

main = print $ showAssociated Foo

Это несколько похоже на функцию копирования, которую вы предлагаете, но преимущества следующие:

  • Избегает повторения (подписи метода "Контекст" )
  • "Show Baz" в контексте несколько более мощный, чем просто функция для отображения "Baz", поскольку она позволяет вам вызывать (библиотечные) функции, которые требуют "Show Baz", или использовать подразумеваемые экземпляры типа "Show [Баз] `:
showAssociateds :: forall a b. Class a => [Associated a b] -> String
showAssociateds as = 
  case getShow :: ShowDict (Associated a b) of
    ShowDict ->
      show as

Основной недостаток заключается в том, что использование getShow всегда требует явной сигнатуры типа (функции, такие как getShowFor, могут смягчить это).

Ответ 2

Как указано указанным @SjoerdVisscher, используя forall в левой части => в class или instance на самом деле не ok, по крайней мере, пока нет, хотя мой конкретный пример работает в ghc-7.4.


Таким образом, это работает:

{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE Rank2Types           #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE UndecidableInstances #-}

class Context c where
  func1 :: c -> String

class (forall b. Context (Associated a b)) => Class a where
  data Associated a :: * -> *

newtype ClassTest = ClassTest { runClassTest :: String }

instance (forall b. Context (Associated ClassTest b)) => Class ClassTest where
  data Associated ClassTest b = ClassTestAssoc b (b -> ClassTest)

instance Context (Associated ClassTest b) where
  func1 (ClassTestAssoc b strFunc) = runClassTest $ strFunc b

main = putStr . func1 $ ClassTestAssoc 37 (ClassTest . show)

Дополнительное ограничение forall b в экземпляре кажется немного уродливым и избыточным, но, по-видимому, оно необходимо.

$runghc-7.4.1 tFamConstraint0.hs
37

Ответ 3

Один идиоматический способ - создать класс Context1. Предположим, что

class Context a where
    func :: a -> String

мы можем обобщить как:

class Context1 f where
    func1 :: Context a => f a -> String

Затем вы даете один экземпляр для всех Associated s:

instance (Context1 (Associated a), Context b) => Context (Associated a b) where
    func = func1

Теперь легко написать класс, который вы хотите, как

instance Context1 (Associated a) => Class a where
    data Associated a :: * -> *

и вы можете быть уверены, что данный контекст Context1 (Associated a) обеспечивает желаемый контекст forall b. Context b => Context (Associated a b).

В Hackage есть много примеров этого шаблона, например Show1, Foldable1 и Traversable1.