Незаконный полиморфный или квалифицированный тип с использованием RankNTypes и TypeFamilies

Я медленно работал над переносом пакета llvm, чтобы использовать типы данных, типы семейств и типы-натов и сталкивался с незначительной проблемой при попытке удалите два новых типа, используемые для классификации значений (ConstValue и Value), введя новый тип Value, параметризованный его константой.

CallArgs принимает только аргументы Value 'Variable a и предоставляет функцию для приведения a Value 'Const a в Value 'Variable a. Я хотел бы обобщить CallArgs, чтобы каждый аргумент был либо 'Const, либо 'Variable. Можно ли это кодировать так или иначе с помощью семейств типов? Я думаю, что это возможно с помощью fundeps.

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}

data Const = Const | Variable

data Value (c :: Const) (a :: *)

type family CallArgs a :: * 
type instance CallArgs (a -> b) = forall (c :: Const) . Value c a -> CallArgs b
type instance CallArgs (IO a)   = IO (Value 'Variable a)

... который не скомпилируется:

/tmp/blah.hs:10:1:
    Illegal polymorphic or qualified type:
      forall (c :: Const). Value c a
    In the type instance declaration for `CallArgs'

Если следующее решение работает (эквивалентно устаревшему коду), но требует, чтобы пользователь использовал каждую константу Value:

type family CallArgs' a :: * 
type instance CallArgs' (a -> b) = Value 'Variable a -> CallArgs' b
type instance CallArgs' (IO a)   = IO (Value 'Variable a)

Ответ 1

CallArgs, который вы запрашиваете, вроде как не детерминированная функция, которая принимает a -> b и возвращает либо Value 'Const a -> blah, либо Value 'Variable a -> blah. Одна вещь, с которой вы иногда можете столкнуться с недетерминированными функциями, - это перевернуть их; действительно, у этого есть детерминированный обратный.

type family   UnCallArgs a
type instance UnCallArgs (Value c a -> b) = a -> UnCallArgs b
type instance UnCallArgs (IO 'Variable a) = IO a

Теперь, где бы вы ни писали тип типа

foo :: CallArgs t -> LLVM t

или что-то в этом роде, вы можете написать это вместо:

foo :: t -> LLVM (UnCallArgs t)

Конечно, вы можете выбрать лучшее имя, чем UnCallArgs, может быть Native или что-то в этом роде, но для этого требуется немного знаний о доменах, которых у меня нет.

Ответ 2

Будет ли обертывание forall c. в работе newtype AV для вас?

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}

data CV = Const | Variable

data Value (c :: CV) (a :: *)

data AV a = AV (forall c. Value c a)

type family CallArgs a :: * 
type instance CallArgs (a -> b) = AV a -> CallArgs b
type instance CallArgs (IO a)   = IO (Value 'Variable a)