Можно ли избежать UndecidableInstances в этом примере?
Я пытаюсь реализовать двоичное дерево как класс типов:
class BinaryTree bt where
empty :: bt a -> Bool
empty = isNothing . root
emptyTree :: bt a
branch :: a -> bt a -> bt a -> bt a
root :: bt a -> Maybe a
lbranch :: bt a -> bt a
rbranch :: bt a -> bt a
И я хочу проиллюстрировать, что двоичные деревья являются контейнерами данных, используя это определение:
instance BinaryTree bt => Functor bt where
fmap f bt = case root bt of
Nothing -> emptyTree
Just a -> branch (f a) (f <$> lbranch bt) (f <$> rbranch bt)
У меня есть:
BinaryTree/BinaryTree.hs:18:10: error:
• The constraint ‘BinaryTree bt’
is no smaller than the instance head ‘Functor bt’
(Use UndecidableInstances to permit this)
• In the instance declaration for ‘Functor bt’
Использование абсолютно заставляет код компилироваться. Но мне интересно, правильно ли это использовать
Обновление 1: О том, «Зачем использовать классы типов»
Это часть исследования, в котором я пытаюсь правильно отличить полные бинарные деревья и совершенные бинарные деревья от обычных бинарных деревьев с помощью кода Haskell, описывая их свойства в коде Haskell. (Свойства: 1) полные двоичные деревья взаимно однозначно соответствуют спискам,
В частности, конечная цель — объявить тип полных двоичных деревьев, который при компиляции отклоняет значение, которое является двоичным деревом, но не полным двоичным деревом. (А также для идеальных бинарных деревьев.)
Исследования по-прежнему не прекращаются, и предложения приветствуются. Большое спасибо!
1 ответ
В неразрешимых случаях
это в основном безобидное расширение. Не волнуйтесь, если вам придется его включить.
В худшем случае
instance A t => B t where ...
instance B t => A t where ...
Если вы будете стараться избегать этого, компилятор будет работать нормально.
Расширение не оказывает вредного воздействия на скомпилированный код, а только на саму компиляцию. Если ваш код компилируется, вы в безопасности.
О перекрывающихся экземплярах
С другой стороны, это повод для беспокойства.
instance BinaryTree bt => Functor bt where
Руководитель этой инстанции
Разрешение экземпляра Haskell вообще не выполняет возврат назад , поскольку это может привести к экспоненциальному увеличению времени компиляции и поломке отдельной компиляции. Нет разумного способа написать что-то вроде
instance MyClass1 f => Functor f where ...
instance MyClass2 f => Functor f where ...
Чтобы это работало, при разрешении Haskell должен сначала попытаться разрешить , и если это не удастся, попробуйте
Обратите внимание, что Haskell позволяет объявлять экземпляры в любом модуле. Итак, чтобы проверить «нет экземпляра для
Итак, экземпляр типа
instance MyClass1 f => Functor f where ...
на самом деле означает «Если вы решаете
Есть несколько способов ослабить ограничения на перекрывающиеся экземпляры, но я не рекомендую их. Они очень хрупкие, если точно не понимать, что происходит под капотом.
Заключение
Чтобы избежать дублирования, вы можете использовать оболочку newtype:
newtype F f a = F (f a)
instance BinaryTree bt => Functor (F bt) where
Даже если это не эквивалентно, вы можете указать суперкласс функтора:
class Functor bt => BinaryTree bt where ...
Это заставит новые экземпляры гарантировать, что деревья являются функторами.
Возможно, что еще более важно, вам также следует подумать о том, чтобы вообще не использовать классы типов для этой задачи. В Haskell довольно необычно объявлять класс типа, если только вы не имеете в виду несколько экземпляров. В вашем случае ваш