Подтвердить что ты не робот

Haskell/GHC UndecidableInstances - пример для проверки без конца?

Я написал некоторый код Haskell, который нужно -XUndecidableInstances для компиляции. Я понимаю, почему это происходит, что есть определенное условие, которое нарушается, и поэтому GHC кричит.

Тем не менее, я никогда не сталкивался с ситуацией, когда средство проверки типов фактически зависало или заканчивалось в бесконечном цикле.

Что представляет собой определение без конечного экземпляра - можете ли вы привести пример?

4b9b3361

Ответ 1

Например:

{-# LANGUAGE UndecidableInstances #-}

class C a where
    f :: a -> a

instance C [[a]] => C [a] where
    f = id

g x = x + f [x]

Что происходит: при вводе f [x] компилятор должен гарантировать, что x :: C [a] для некоторого a. В объявлении экземпляра указано, что x может иметь тип C [a], только если он также имеет тип C [[a]]. Поэтому компилятор должен гарантировать, что x :: C [[a]] и т.д. И попадает в бесконечный цикл.

См. также Может ли использование неприменимых изменений pragma локально иметь глобальные последствия для завершения компиляции?

Ответ 2

Там классический, несколько тревожный пример (с участием взаимодействия с функциональными зависимостями) в этот документ из HQ:

class Mul a b c | a b -> c where
  mul :: a -> b -> c
instance Mul a b c => Mul a [b] [c] where
  mul a = map (mul a)

f b x y = if b then mul x [y] else y

Нам нужно mul x [y] иметь тот же тип, что и y, поэтому, принимая x :: x и y :: y, нам нужно

instance Mul x [y] y

который, согласно данному экземпляру, может иметь. Разумеется, для некоторого z мы должны взять y ~ [z] такое, что

instance Mul x y z

то есть.

instance Mul x [z] z

и мы находимся в цикле.

Это вызывает беспокойство, поскольку этот экземпляр Mul выглядит так, что его рекурсия структурно уменьшается, в отличие от явно патологического примера в ответе Петра. Тем не менее, он делает цикл GHC (с порогом скуки, чтобы избежать зависания).

Проблема, поскольку я уверен, что я уже упоминал где-то, что идентификация y ~ [z] выполняется, несмотря на то, что z функционально зависит от y. Если бы мы использовали функциональную нотацию для функциональной зависимости, мы могли бы заметить, что ограничение говорит y ~ Mul x [y] и отклоняет подстановку как в случае нарушения проверки на наличие.

Заинтригованный, я думал, что дам это вихрем,

class Mul' a b where
  type MulT a b
  mul' :: a -> b -> MulT a b

instance Mul' a b => Mul' a [b] where
  type MulT a [b] = [MulT a b]
  mul' a = map (mul' a)

g b x y = if b then mul' x [y] else y

При включенном UndecidableInstances для цикла требуется довольно много времени. При отключенном UndecidableInstances экземпляр по-прежнему принимается, и typechecker все еще работает, но обрезание происходит намного быстрее.

Итак... смешной старый мир.