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

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

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

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

изменить: я хотел бы сохранить совместимость с GHC 7.0.3


person Boris    schedule 29.06.2012    source источник


Ответы (3)


У меня нет 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

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

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

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

person FunctorSalad    schedule 30.06.2012

Как было указано @SjoerdVisscher, используя forall слева от => в class или instance на самом деле не нормально, по крайней мере пока, хотя мой конкретный пример работает в 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

person leftaroundabout    schedule 29.06.2012
comment
Спасибо, но, к сожалению, отсутствие совместимости с GHC 7.0 было бы для меня серьезным недостатком, потому что этот код будет использоваться в библиотеке. Но мне нравится это решение, и я буду использовать его, когда GHC 7.4+ станет повсеместным. - person Boris; 29.06.2012
comment
Если вам нужна максимальная совместимость, вам, вероятно, лучше тупо скопировать все методы класса, хотя это немного неудобно. - person leftaroundabout; 30.06.2012
comment
@leftaroundabout SPJ говорит, что это не должно работать: hackage.haskell.org/ трассировка/ghc/ticket/7019#comment:3 - person Sjoerd Visscher; 30.06.2012
comment
@SjoerdVisscher: спасибо! Я мало что понимаю в вопросах реализации, но мне кажется, что этот материал с количественными контекстами в принципе желателен и может в какой-то момент работать правильно, поэтому я оставлю ответ здесь с предупреждающим сообщением; или вы считаете это неуместным? - person leftaroundabout; 30.06.2012
comment
Используя Data.Constraint.Forall: вы можете написать class ForallF Context (Associated a) => Class a как а также instance ForallF Context (Associated ClassTest) => Class ClassTest - person Iceland_jack; 13.09.2016

Одним из идиоматических способов является создание класса Context1. Предположим, у нас есть

class Context a where
    func :: a -> String

мы могли бы обобщить как:

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

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

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 и Проходимый1.

person Daniel Wagner    schedule 17.09.2015