Проблема конфликта достаточно проста.Правила для перекрывающихся семейств типов довольно просты:
Объявления экземпляров семейства типов, используемые в одной программе, могут перекрываться, только если правые части перекрывающихся экземпляровсовпадают для перекрывающихся типов.Более формально, два объявления экземпляров перекрываются, если есть замена, которая делает синтаксически левые части экземпляров одинаковыми.В любом случае, правые части экземпляров также должны быть синтаксически равны при одной и той же замене.
Обратите внимание, что синтаксически указывает равно.Рассмотрим эти два случая:
instance Split Two where
type Lo Two = One -- 0 mod 2
type Hi Two = One -- 1 mod 2
instance Split (Succ n) where
type Lo (Succ n) = Lo (n)
type Hi (Succ n) = Succ (Hi n)
Two
определяется как Succ One
, и синонимы простого типа расширяются в целях синтаксического равенства, поэтому они равны с левой стороны;но это не правая часть, отсюда и ошибка.
Возможно, вы заметили, что я удалил контекст класса из приведенного выше кода.Более глубокая проблема, и, возможно, почему вы не ожидали возникновения вышеуказанного конфликта, состоит в том, что дубликаты действительно являются конфликтующими дубликатами.Контексты классов, как всегда, игнорируются для целей выбора экземпляров, и, если память служит мне, это удваивается для связанных семейств типов, что в значительной степени является синтаксическим удобством для неассоциированных семейств типов и может не ограничиваться классом способами, которые выожидайте.
Теперь ясно, что эти два экземпляра должны отличаться, и вы хотите выбирать между ними на основе результата использования Compare
, поэтому этот результат должен быть параметр класса типа, а не просто ограничение.Вы также смешиваете семейства типов с функциональными зависимостями, что излишне неудобно.Итак, начиная с вершины, мы оставим основные типы:
-- type-level naturals
data One
data Succ n
type Two = Succ One
-- type-level ordering
data LT
data EQ
data GT
Перепишите функцию Compare
как семейство типов:
type family Compare n m :: *
type instance Compare One One = EQ
type instance Compare (Succ n) One = GT
type instance Compare One (Succ n) = LT
type instance Compare (Succ n) (Succ m) = Compare n m
Теперь для обработки условныхнам понадобится какое-то семейство типа «управление потоком».Я определю здесь что-то более общее для назидания и вдохновения;специализируюсь по вкусу.
Мы дадим ему предикат, вход и две ветви на выбор:
type family Check pred a yes no :: *
Нам понадобится предикат для тестирования Compare
's результат:
data CompareAs a
type instance (CompareAs LT) LT yes no = yes
type instance (CompareAs EQ) LT yes no = no
type instance (CompareAs GT) LT yes no = no
type instance (CompareAs LT) EQ yes no = no
type instance (CompareAs EQ) EQ yes no = yes
type instance (CompareAs GT) EQ yes no = no
type instance (CompareAs LT) GT yes no = no
type instance (CompareAs EQ) GT yes no = no
type instance (CompareAs GT) GT yes no = yes
Это ужасно утомительный набор определений, и прогноз довольно мрачный для сравнения больших наборов значений типов.Существуют более расширяемые подходы (псевдоподобный тег и биекция с естественными, являющимися очевидным и эффективным решением), но это немного выходит за рамки этого ответа.Я имею в виду, что мы просто делаем вычисления на уровне типов, давайте не будем смешными или чем-то еще.
В этом случае проще было бы просто определить функцию анализа случая по результатам сравнения:
type family CaseCompare cmp lt eq gt :: *
type instance CaseCompare LT lt eq gt = lt
type instance CaseCompare EQ lt eq gt = eq
type instance CaseCompare GT lt eq gt = gt
Я пока воспользуюсь последним, но если вам нужны более сложные условия в других местах, общий подход начинает приобретать больше смысла.
В любом случае.Мы можем разделить класс ... er, Split
на семейства несвязанных типов:
data Oops
type family Lo n :: *
type instance Lo Two = One
type instance Lo (Succ (Succ n))
= CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n)))
Oops -- yay, type level inexhaustive patterns
(Succ n)
(Lo (Succ n))
type family Hi n :: *
type instance Hi Two = One
type instance Hi (Succ (Succ n))
= CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n)))
Oops -- yay, type level inexhaustive patterns
One
(Succ (Hi (Succ n)))
Наиболее важным моментом здесь является (на первый взгляд, избыточность) использование (Succ (Succ n))
- причина этого заключается в том, чточто два Succ
конструктора необходимы для того, чтобы отличить аргумент от Two
, что позволяет избежать ошибок конфликта, которые вы видели.
Обратите внимание, что для простоты я переместил все в семейства типов, поскольку все это полностью тип-уровень.Если вы также хотите обрабатывать значения по-разному в зависимости от приведенных выше вычислений - в том числе косвенно, через тип Mod
- вам может потребоваться добавить соответствующие определения классов обратно, поскольку они необходимы для выбора терминов на основе типа, а не простовыбор типов на основе типов.