制約を追跡する手法


322

シナリオは次のとおりです。型シグネチャを使用していくつかのコードを記述しましたが、GHCは一部のxとについてx〜yを推定できませんでしたy。通常、GHCに骨を投げて、関数の制約に同型を単に追加できますが、これはいくつかの理由で悪い考えです。

  1. コードの理解を強調するものではありません。
  2. 最終的に5つの制約で十分な場合があります(たとえば、5がもう1つの特定の制約によって暗示されている場合)
  3. 何か間違ったことをしたり、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)。基本的に、正しい制約に出くわしました。それらを見つけるための体系的な方法はまだありません。

私の質問は:

  1. なぜGHCはその制約を提案したのですか?構文のどこにも制約はありInternal a ~ Internal bませんが、GHCはどこからそれを引き出しましたか?
  2. 一般的に、GHCが必要とする制約の起源を追跡するためにどのような手法を使用できますか?私自身発見できる制約についても、私のアプローチは本質的に、再帰的制約を物理的に書き留めることによって、問題のあるパスを力ずくで強制します。このアプローチは基本的に制約の無限のウサギの穴を掘り下げており、私が想像できる最も効率の悪い方法についてです。

21
型レベルのデバッガーについていくつかの議論がありましたが、一般的なコンセンサスは、型チェッカーの内部ロジックが役に立たないことを示しているようです:/現在のところ、Haskellの制約ソルバーはくだらない不透明ロジック言語です:)
Daniel Gratzer

12
@jozefgそのディスカッションへのリンクはありますか?
crockeea 2014

36
多くの場合、型シグネチャを完全に削除して、シグネチャがどうあるべきかをghciに通知させると役立ちます。
Tobias Brandt

12
どういうわけかabバインドされている- -あなたのコンテキストの型シグネチャの外を見てa -> (a -> b) -> a、ではありませんa -> (a -> b) -> b。多分それだけですか?制約ソルバーを使用すると、それらはどこでも推移的等式に影響を与える可能性がありますが、エラーは通常、制約が発生した場所に「近い」場所を示します。@jozefgを使用してもかっこいいです-制約にタグなどで注釈を付けて、それらがどこから来たかを示すことはできますか?:s
Athan Clark

回答:


6

まず、関数の型が間違っています。私はそれが(文脈なしで)あるべきだとかなり確信していますa -> (a -> b) -> b。GHC 7.10は、元のコードでは制約の欠落について不平を言うため、それを指摘するのに多少役立ちます Internal (a -> b) ~ (Internal a -> Internal a)shareのタイプを修正した後も、GHC 7.10は引き続きガイドに役立ちます。

  1. Could not deduce (Internal (a -> b) ~ (Internal a -> Internal b))

  2. 上記を追加した後、 Could not deduce (sup ~ Domain (a -> b))

  3. それを追加した後、我々が得るCould not deduce (Syntactic a)Could not deduce (Syntactic b)Could not deduce (Syntactic (a -> b))

  4. これら3つを追加した後、最終的に型チェックを行います。だから私たちは

    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) ~ tWantedセットに追加されているかを確認できますが、これは非常に長い読み取りになります。


0

GHC 8.8以降でこれを試しましたか?

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

重要なのは、制約間でタイプホールを使用することです。 _ => your difficult type

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.