Незаконный полиморфный или квалифицированный тип с использованием 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)