web-dev-qa-db-ja.com

-XAllowAmbiguousTypesはいつ適切ですか?

私は最近shareの定義に関して question について syntactic-2. を投稿しました。私はこれをGHC 7.6で動作させました:

{-# LANGUAGE GADTs, TypeOperators, FlexibleContexts #-}

import Data.Syntactic
import Data.Syntactic.Sugar.BindingT

data Let a where
    Let :: Let (a :-> (a -> b) :-> Full b)

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

ただし、GHC 7.8では-XAllowAmbiguousTypesその署名でコンパイルします。または、fi

(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))

これは、SyntacticNのFundepによって暗示されるタイプです。これにより、拡張を回避できます。もちろんこれは

  • すでに大きな署名に追加する非常に長いタイプ
  • 手動で導出するのは面倒
  • fundepにより不要

私の質問は:

  1. これは-XAllowAmbiguousTypes
  2. 一般に、この拡張機能はいつ使用する必要がありますか?答え here は、「ほとんど決して良いアイデアではない」ことを示唆しています。
  3. ドキュメント を読みましたが、制約があいまいであるかどうかを判断するのにまだ苦労しています。具体的には、Data.Syntactic.Sugarの次の関数を検討してください。

    sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) 
             => sub sig -> f
    sugarSym = sugarN . appSym
    

    ここでは、fi(および場合によってはsup)があいまいであるように見えますが、拡張子なしでコンパイルされます。 sugarSymが明確なのにshareは明確なのはなぜですか? sharesugarSymのアプリケーションであるため、share制約はすべてsugarSymから直接取得されます。

211
crockeea

sugarSymのシグニチャーがこれらの正確なタイプ名を使用する構文の公開バージョンは表示されないため、 コミット8cfd02 ^の開発ブランチ を使用します。それらの名前を使用しました。

それでは、なぜGHCはfiについてではなく、あなたのタイプ署名のsugarSymについて文句を言うのでしょうか?リンクしたドキュメントでは、制約が機能的な依存関係を使用して他の曖昧でないタイプから曖昧なタイプを推測しない限り、制約の右側に表示されない場合、タイプは曖昧であると説明されています。それでは、2つの関数のコンテキストを比較して、機能的な依存関係を探しましょう。

_class ApplySym sig f sym | sig sym -> f, f -> sig sym
class SyntacticN f internal | f -> internal

sugarSym :: ( sub :<: AST sup
            , ApplySym sig fi sup
            , SyntacticN f fi
            ) 
         => sub sig -> f

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

sugarSymの場合、あいまいでないタイプはsubsig、およびfであり、それらから、他のすべてを明確にするために機能的な依存関係に従うことができるはずですコンテキストで使用されるタイプ、つまりsupおよびfi。そして実際、SyntacticNの_f -> internal_機能依存性はfを使用してfiを明確にし、その後ApplySymの_f -> sig sym_機能依存性はfi(およびsup、これは既にあいまいではありませんでした)を明確にするために、新しく明確化されたsig。それで、sugarSymAllowAmbiguousTypes拡張を必要としない理由を説明します。

sugarを見てみましょう。私が最初に気づくのは、コンパイラがnotあいまいな型ではなく、インスタンスの重複について不平を言っていることです:

_Overlapping instances for SyntacticN b fi
  arising from the ambiguity check for ‘share’
Matching givens (or their superclasses):
  (SyntacticN (a -> (a -> b) -> b) fi1)
Matching instances:
  instance [overlap ok] (Syntactic f, Domain f ~ sym,
                         fi ~ AST sym (Full (Internal f))) =>
                        SyntacticN f fi
    -- Defined in ‘Data.Syntactic.Sugar’
  instance [overlap ok] (Syntactic a, Domain a ~ sym,
                         ia ~ Internal a, SyntacticN f fi) =>
                        SyntacticN (a -> f) (AST sym (Full ia) -> fi)
    -- Defined in ‘Data.Syntactic.Sugar’
(The choice depends on the instantiation of ‘b, fi’)
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
_

したがって、私がこの権利を読んでいる場合、GHCはあなたの型が曖昧であると考えているのではなく、むしろ、あなたの型が曖昧であるかどうかを確認しながら、GHCは別の別の問題に遭遇しました。そして、GHCにあいまいさチェックを実行しないように指示した場合、その別の問題は発生しなかったことを伝えます。これは、AllowAmbiguousTypesを有効にするとコードをコンパイルできる理由を説明しています。

ただし、重複するインスタンスの問題は残ります。 GHCによってリストされた2つのインスタンス(_SyntacticN f fi_およびSyntacticN (a -> f) ...)は互いに重複しています。奇妙なことに、これらの最初のインスタンスは他のインスタンスと重複しているように見えますが、これは疑わしいです。 _[overlap ok]_はどういう意味ですか?

SyntacticはOverlappingInstancesでコンパイルされていると思います。 コード を見ると、実際にそうなっています。

少し実験してみると、GHCは、一方が厳密にもう一方よりも一般的であることが明らかな場合、インスタンスが重複しても問題ないようです。

_{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo a where
  whichOne _ = "a"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- [a]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])
_

しかし、GHCは、どちらも明らかに他のインスタンスよりも適切である場合、インスタンスが重複しても大丈夫ではありません。

_{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo (f Int) where  -- this is the line which changed
  whichOne _ = "f Int"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- Error: Overlapping instances for Foo [Int]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])
_

タイプシグネチャはSyntacticN (a -> (a -> b) -> b) fiを使用し、_SyntacticN f fi_もSyntacticN (a -> f) (AST sym (Full ia) -> fi)も他のものよりも適しています。型署名のその部分を_SyntacticN a fi_またはSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)に変更しても、GHCは重複について不平を言いません。

もし私があなただったら、 これら2つの可能なインスタンスの定義 を見て、これら2つの実装のどちらかがあなたが望むものかどうかを判断します。

12
gelisam