Методы отслеживания ограничений

Здесь сценарий: я написал код с сигнатурой типа, и жалобы GHC не смогли вывести x ~ y для некоторых x и y. Обычно вы можете бросить GHC в кость и просто добавить изоморфизм к функциональным ограничениям, но это плохая идея по нескольким причинам:

  • Он не подчеркивает понимание кода.
  • В результате вы можете ограничить 5 ограничениями, которых было бы достаточно (например, если 5 подразумевается еще одним конкретным ограничением)
  • Вы можете в конечном итоге с фиктивными ограничениями, если вы сделали что-то неправильно или если GHC бесполезен

Я просто провел несколько часов, сражаясь с футляром 3. Я играю с syntactic-2.0, и я пытался определить независимую от домена версия share, похожая на версию, определенную в NanoFeldspar.hs.

У меня было это:

{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators #-}
import Data.Syntactic

-- Based on NanoFeldspar.hs
data Let a where
    Let :: Let (a :-> (a -> b) :-> Full b)

share :: (Let :<: sup,
          Domain a ~ sup,
          Domain b ~ sup,
          SyntacticN (a -> (a -> b) -> b) fi) 
      => a -> (a -> b) -> a
share = sugarSym Let

и GHC could not deduce (Internal a) ~ (Internal b), что, конечно же, не то, что я собирался сделать. Так что либо я написал код, который я не собирался (который требовал ограничения), либо GHC хотел этого ограничения из-за некоторых других ограничений, которые я написал.

Оказывается, мне нужно добавить (Syntactic a, Syntactic b, Syntactic (a->b)) в список ограничений, ни один из которых не подразумевает (Internal a) ~ (Internal b). Я в основном наткнулся на правильные ограничения; У меня до сих пор нет систематического способа найти их.

Мои вопросы:

  • Почему GHC предложила это ограничение? Нигде в синтаксисе есть ограничение Internal a ~ Internal b, поэтому, где GHC вытащил это из?
  • В целом, какие методы можно использовать для отслеживания происхождения ограничения, которое, по мнению GHC, ему необходимо? Даже для ограничений, которые я могу обнаружить сам, мой подход по сути грубым, заставляя оскорбительный путь, физически записывая рекурсивные ограничения. Этот подход в основном сводится к бесконечной кроличьей дыре ограничений и представляет собой наименее эффективный метод, который я могу себе представить.

Ответ 1

Прежде всего, ваша функция имеет неправильный тип; Я уверен, что это должно быть (без контекста) a -> (a -> b) -> b. GHC 7.10 несколько более полезен, указав это, потому что с вашим исходным кодом он жалуется на недостающее ограничение Internal (a -> b) ~ (Internal a -> Internal a). После фиксации типа share GHC 7.10 остается полезным в руководстве нас:

  • Could not deduce (Internal (a -> b) ~ (Internal a -> Internal b))

  • После добавления выше мы получим Could not deduce (sup ~ Domain (a -> b))

  • После добавления этого значения получим Could not deduce (Syntactic a), Could not deduce (Syntactic b) и Could not deduce (Syntactic (a -> b))

  • После добавления этих трех, это, наконец, typechecks; поэтому мы получаем

    share :: (Let :<: sup,
              Domain a ~ sup,
              Domain b ~ sup,
              Domain (a -> b) ~ sup,
              Internal (a -> b) ~ (Internal a -> Internal b),
              Syntactic a, Syntactic b, Syntactic (a -> b),
              SyntacticN (a -> (a -> b) -> b) fi)
          => a -> (a -> b) -> b
    share = sugarSym Let
    

Итак, я бы сказал, что GHC не был бесполезен в руководстве нас.

Что касается вашего вопроса о трассировке, где GHC получает свои требования ограничения, вы можете попробовать флаги отладки GHC, в частности, -ddump-tc-trace, а затем прочитайте полученный журнал, чтобы увидеть, где Internal (a -> b) ~ t и (Internal a -> Internal a) ~ t добавлены в набор Wanted, но это будет довольно длинным.