Как установить ограничения на связанные данные?
Я хотел бы указать, что связанные данные всегда являются экземплярами определенного класса.
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.