Как неразрешимые экземпляры могут фактически повесить компилятор?
К тому времени, когда я впервые прочитал серьезную критику-XUndecidableInstances
Я уже полностью привык к этому, рассматривая это как простое снятие раздражающего ограничения, которое Haskell98 должен сделать компиляторами проще в реализации.
На самом деле я сталкивался с множеством приложений, в которых требовались неразрешимые экземпляры, но ни в одном из них они не вызывали проблем, связанных с неразрешимостью. Пример Люка проблематичен по совершенно другой причине
class Group g where
(%) :: g -> g -> g
...
instance Num g => Group g where
...
- ну, это будет явно перекрываться любым надлежащим Group
Например, неразрешимость - это наименьшее из наших беспокойств: это на самом деленедетерминировано!
Но, честно говоря, я сохранил "неразрешимые экземпляры, которые могут повесить компилятор" в моей голове.
Откуда он был приобретен, когда я прочитал этот вызов на CodeGolf.SE, с просьбой о коде, который будет бесконечно зависать компилятор. Ну, звучит как работа для неразрешимых случаев, верно?
Оказывается, я не могу заставить их сделать это. Следующие компиляции в кратчайшие сроки, по крайней мере, из GHC-7.10:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
class C y
instance C y => C y
main = return ()
Я даже могу использовать методы класса, и они будут вызывать цикл только во время выполнения:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
class C y where y::y
instance C y => C y where y=z
z :: C y=>y; z=y
main = print (y :: Int)
Но циклы времени выполнения не являются чем-то необычным, вы можете легко кодировать их в Haskell98.
Я также пробовал разные, менее прямые петли, такие как
{-# LANGUAGE FlexibleContexts, UndecidableInstances #-}
data A x=A
data B x=B
class C y
instance C (A x) => C (B x)
instance C (B x) => C (A x)
Опять же, нет проблем во время компиляции.
Итак, что на самом деле нужно, чтобы повесить компилятор в разрешении неразрешимых экземпляров классов типов?
3 ответа
Я не думаю, что я когда-либо повесил компилятор. Я могу получить переполнение стека, изменив ваш первый пример. Кажется, что происходит некоторое кэширование, поэтому нам нужно требовать бесконечную последовательность уникальных ограничений, например,
data A x = A deriving (Show)
class C y where get :: y
instance (C (A (A a))) => C (A a) where
get = A
main = print (get :: A ())
что дает нам
• Reduction stack overflow; size = 201
When simplifying the following type:
C (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A (A ())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
Use -freduction-depth=0 to disable this check
(any upper bound you could choose might fail unpredictably with
minor updates to GHC, so disabling the check is recommended if
you're sure that type checking should terminate)
который говорит вам, как вы можете заставить его висеть, если вы действительно этого хотите. Я предполагаю, что если вы можете заставить его зависать без этого, вы нашли ошибку.
Я хотел бы услышать от кого-то, кто работает на GHC.
Самый простой способ получить "переполнение стека сокращения" - это использовать семейства типов:
type family Loop where
Loop = Loop
foo :: Loop
foo = True
Я не знаю прямого способа получить на самом деле циклическую компиляцию на текущем GHC. Я вспоминаю получение циклов пару раз с GHC 7.11, но я помню только один в воспроизводимых деталях:
data T where
T :: forall (t :: T). T
Но это было исправлено с тех пор.
К моему удивлению, UndecidableInstances
может на самом деле повесить определенные версии GHC. Вот несколько строк кода, которые сделали это для меня:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Nested where
class Nested r ix where
type family Lower ix :: *
data LN
instance Nested LN (Lower ix) => Nested LN ix where
data L
instance Nested LN ix => Nested L ix where
При компиляции как часть библиотеки (не напрямую ghc main.hs
) этот код бесконечно висит на GHC 8.2.1
Как уже упоминалось @luqui, это похоже на ошибку, поэтому об этом сообщается так: https://ghc.haskell.org/trac/ghc/ticket/14402
Редактировать: Это оказалось действительно ошибкой, которая уже была исправлена в текущей версии разработки GHC.