Списки типов с ограничениями
Я пытаюсь создать список на уровне типов, но у меня возникают некоторые затруднения с выяснением того, как применять ограничения.
Мой базовый код:
data Foo z q = Foo1 (z q)
| Foo2 (z q)
class Qux q -- where ...
class Baz z -- where ...
class Bar a where -- a has kind *->*
type BCtx a q :: Constraint -- using ConstraintKinds to allow constraints on the concrete type
f :: (BCtx a q) => a q -> a q -> a q
g :: (BCtx a q, BCtx a q') => a q -> a q'
instance (Baz z) => Bar (Foo z) where
type BCtx (Foo z) q = (Num (z q), Qux q) -- for example
f (Foo1 x) (Foo1 y) = Foo1 $ x+y -- these functions need access to the type q to do arithmetic mod q
f (Foo1 x) (Foo2 y) = Foo2 $ x-y
-- ...
Вы можете думать о q
выше, представляющий основные полномочия. Я также хотел бы представлять составные числа, используя список типов qi
s. Я представляю что-то вроде:
data QList qi qs = QCons qi qs
| QNil
с данными
data FList c q = FNil
| FCons (c (Head q)) (FList c (Tail q))
где (Head q)
должен соответствовать qi
а также (Tail q)
должен соответствовать qs
, Обратите внимание, что q
параметр для FList
НЕ (обязательно) (Qux q)
, это список (Qux qi)
, (Я не хочу более подробно рассказывать об этом списке, так как это одна из проблем дизайна, которую я представляю). Я хотел бы работать "по модулю" на FList
:
instance (Bar c) => Bar (FList c) where
type BCtx (FList c) q = () -- Anything I put here is not enough
f (FCons x xs) (FCons y ys) = FCons (f x y) (f xs ys)
-- the left call to `f` calls a concrete instance, the right call to `f` is a recursive call on the rest of the list
-- ...
Компилирование этих фрагментов кодов вместе в GHC приводит к (по модулю транскрипции, абстракции и ошибкам ввода):
Could not deduce (BCtx c (Head q), BCtx c (Tail q))
а потом
Could not deduce (BCtx c (Head (Tail q)), BCtx c (Tail (Tail q)))
и т.п.
Я понимаю, почему я получаю эту ошибку, но не как ее исправить.
Конкретно я ожидаю FList c q
типа где c~Foo z
а также q~QCons q1 (QCons q2 QNil)
и, конечно же, мой список будет удовлетворять всем ограничениям BCtx на каждом уровне.
Я не уверен, что исправление этих конкретных ошибок приведет к компиляции кода, но это только начало. Весь класс Bar в основном фиксированный (требуется тип Constraint, а экземпляры Bar должны иметь вид * -> *). Я не верю, что могу использовать экзистенциальные типы для создания списка универсальных объектов, потому что мне нужен доступ к qi
параметр. Я готов изменить тип FList
а также QList
чтобы позволить мне работать по модулю над коллекцией баров.
Спасибо за ваше время!
1 ответ
Для обработки списков типов необходимо отличать пустые списки от непустых и обрабатывать их отдельно. Ошибки "Не удалось вывести" в вашем коде происходят из-за того, что ваш экземпляр принимает непустой список, хотя на самом деле список может быть или не быть пустым. Вот решение с использованием расширений TypeFamilies
, TypeOperators
, DataKinds
, а также GADTs
,
С DataKinds
, списки типов предварительно определены. У них есть вид [*]
, но они будут использоваться в контексте, где вид *
ожидается, поэтому оператор должен их привести:
data InjList (qs :: [*])
Используя списки типов, FList
определяется как
data FList c q where
FNil :: FList c (InjList '[])
FCons :: c h -> FList c (InjList t) -> FList c (InjList (h ': t))
Он определяется как GADT, чтобы выразить, как можно только построить FList
с над типом InjList q'
для некоторого списка типов q'
, Например, термин FCons [True] FNil
имеет тип FList [] (InjList (Bool ': '[]))
, С другой стороны, так как Bool
не в форме InjList q'
, нет терминов (кроме ⊥) типа FList [] Bool
, Путем сопоставления с образцом на FList
, функция может проверить, что ей был передан не-аргумент, и дополнительно определить, был ли ей передан пустой список типов.
Экземпляр Bar
за FList
s должен обрабатывать ноль списков и списков минусов отдельно. Нулевой список имеет пустой контекст. У списка минусов есть компоненты для головы и хвоста списка. Это выражается сопоставлением с образцом в списке типов в связанном экземпляре типа BCtx
, Функция f
проверяет его аргумент, чтобы убедиться, что это не ⊥, и решить, является ли он пустым списком.
instance (Bar c) => Bar (FList c) where
-- Empty context for '[]
type BCtx (FList c) (InjList '[]) = ()
-- Context includes components for head and tail of list
type BCtx (FList c) (InjList (h ': t)) = (BCtx c h, BCtx (FList c) (InjList t))
f FNil FNil = FNil
f (FCons x xs) (FCons y ys) = FCons (f x y) (f xs ys)
Мы можем загрузить код в GHCi, чтобы убедиться, что он работает:
instance Bar [] where
type BCtx [] q = Num q
f xs ys = zipWith (+) xs ys
instance Show (FList c (InjList '[])) where
show FNil = "FNil"
instance (Show (c h), Show (FList c (InjList t))) => Show (FList c (InjList (h ': t))) where
show (FCons h t) = "FCons (" ++ show h ++ ") (" ++ show t ++ ")"
$ ghci
> :load Test
> f (FCons [1,2] FNil) (FCons [3,4] FNil)
FCons ([4,6]) (FNil)