Методы отслеживания ограничений
Здесь сценарий: я написал код с сигнатурой типа, и жалобы 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
, но это будет довольно длинным.