Как определить, почему ghc ищет конкретный экземпляр класса?

Когда экземпляры класса условного типа работают вглубь, может быть трудно понять, почему ghc жалуется на экземпляр класса отсутствующего типа. Например:

class MyClass1 c
class MyClass2 c
class MyClass3 c

data MyType1 a
data MyType2 a

instance MyClass1 a => MyClass2 (MyType1 a)
instance MyClass2 a => MyClass3 (MyType2 a)

foo :: (MyClass3 c) => c
foo = undefined

bar :: MyType2 (MyType1 Int)
bar = foo

GHC дает следующую ошибку:

Example.hs:149:7-9: error:
    • No instance for (MyClass1 Int) arising from a use of ‘foo
    • In the expression: foo
      In an equation for ‘bar: bar = foo
    |
149 | bar = foo
    |       ^^^

Предположим, что я только написал определения для foo и bar, а все остальное было импортированным кодом, который я не писал, я могу быть очень смущен, почему ghc пытается найти экземпляр MyClass1 для Int. Это может быть даже в первый раз, когда я когда-либо слышал о классе MyClass1, если до сих пор я полагался на импортированные экземпляры. Было бы неплохо, если бы ghc мог дать мне "трассировку стека" в цепочке экземпляров класса типа, например

Sought (MyClass2 (MyType1 Int)) to satisfy (MyClass3 (MyType2 (MyType1 Int))) from conditional type class instance OtherModule.hs:37:1-18
Sought (MyClass1 Int) to satisfy (MyClass2 (MyType1 Int)) from conditional type class instance OtherModule.hs:36:1-18

У ghc есть опция командной строки для этого? Если нет, как мне отладить это?

Имейте в виду, что моя реальная проблема намного сложнее, чем этот простой пример. например

Search.hs:110:31-36: error:
    • Could not deduce (Ord
                          (Vars (DedupingMap (Rep (Index gc)) (IndexedProblem ac))))
        arising from a use of ‘search
      from the context: (PP gc (IndexedProblem ac),
                         Show (Vars (DedupingMap (Rep (Index gc)) (IndexedProblem ac))),
                         Foldable f, MonadNotify m)
        bound by the type signature for:
                   searchIndexedReplicaProblem :: forall gc ac (f :: * -> *) (m :: *
                                                                                   -> *).
                                                  (PP gc (IndexedProblem ac),
                                                   Show
                                                     (Vars
                                                        (DedupingMap
                                                           (Rep (Index gc)) (IndexedProblem ac))),
                                                   Foldable f, MonadNotify m) =>
                                                  f (Index
                                                       (Clzs
                                                          (PartitionedProblem
                                                             gc (IndexedProblem ac))))
                                                  -> m (Maybe
                                                          (Vars
                                                             (PartitionedProblem
                                                                gc (IndexedProblem ac))))
        at Search.hs:(103,1)-(109,131)
    • In the expression: search
      In an equation for ‘searchIndexedReplicaProblem:
          searchIndexedReplicaProblem = search
    |
110 | searchIndexedReplicaProblem = search
    |                               ^^^^^^

Существует пять условий покрытия для PP, и я использую семейства типов и нерешимые экземпляры, поэтому крайне неясно, почему ghc дает мне свою ошибку. Какие инструменты можно использовать для отслеживания проблемы?

Ответ 1

Вы можете попробовать параметр -ddump-cs-trace, хотя он предназначен для помощи разработчикам GHC при отладке ограничений, решающих код, но он может быть вам и полезен. Вот результат для вашего примера:

Step 1[l:2,d:0] Kept as inert:
    [G] $dMyClass3_a1rt {0}:: MyClass3 c_a1rs[sk:2]
Step 2[l:2,d:0] Dict equal (keep):
    [WD] $dMyClass3_a1rv {0}:: MyClass3 c_a1rs[sk:2]
Constraint solver steps = 2
Step 1[l:1,d:0] Top react: Dict/Top (solved wanted):
    [WD] $dMyClass3_a2uc {0}:: MyClass3 (MyType2 (MyType1 Int))
Step 2[l:1,d:1] Top react: Dict/Top (solved wanted):
    [WD] $dMyClass2_a2up {1}:: MyClass2 (MyType1 Int)
Step 3[l:1,d:2] Kept as inert:
    [WD] $dMyClass1_a2uq {2}:: MyClass1 Int
Step 4[l:2,d:0] Kept as inert:
    [G] $dMyClass3_a1rB {0}:: MyClass3 c_a1rz[sk:2]
Step 5[l:2,d:0] Wanted CallStack IP:
    [WD] $dIP_a2u8 {0}:: ?callStack::GHC.Stack.Types.CallStack
Step 6[l:2,d:0] Kept as inert:
    [WD] $dIP_a2uA {0}:: ?callStack::GHC.Stack.Types.CallStack
Step 7[l:2,d:0] Kept as inert:
    [G] $dMyClass2_a2uh {0}:: MyClass2 a_a2ug[ssk:2]
Step 8[l:2,d:0] Kept as inert:
    [G] $dMyClass1_a2ul {0}:: MyClass1 a_a2uk[ssk:2]
Constraint solver steps = 8

Нелегко извлечь полезную информацию из этого дампа, но AFAIK это единственный доступный вариант прямо сейчас. Немного связанных билетов: 13443, 15044

ADDED: Я попытаюсь немного объяснить, что означает свалка. Я действительно не знаком с внутренними структурами GHC, так что это просто мое (возможно, неправильное) понимание.

Соответствующий бит следующий:

Step 1[l:1,d:0] Top react: Dict/Top (solved wanted):
    [WD] $dMyClass3_a2uc {0}:: MyClass3 (MyType2 (MyType1 Int))
Step 2[l:1,d:1] Top react: Dict/Top (solved wanted):
    [WD] $dMyClass2_a2up {1}:: MyClass2 (MyType1 Int)
Step 3[l:1,d:2] Kept as inert:
    [WD] $dMyClass1_a2uq {2}:: MyClass1 Int

Здесь d обозначает "глубина", WD - "требуемое производное" ограничение. Таким образом, у нас есть что - то вроде трассировки стека разыскиваемых ограничений: сначала мы хотели MyClass3 (MyType2 (MyType1 Int)), то мы нашли MyClass3 экземпляр для MyType2, и теперь мы хотим MyClass2 (MyType1 Int), чтобы удовлетворить его. Затем мы нашли экземпляр MyClass2 для MyType1, теперь мы хотим, чтобы MyClass1 Int удовлетворял его. Я знаю, объяснение расплывчато, но это все, что у меня есть для вас, извините.