OverlappingInstances コンパイラ フラグを使用することで大きなメリットが得られるライブラリを設計しています。しかし、誰もがこの拡張機能について悪口を言い、その危険性を警告しています。私の質問は、ハックのどこかにこの拡張機能をうまく使用した例はありますか? 悪さをカプセル化し、拡張機能を適切に使用する方法に関する経験則はありますか?
3 に答える
おそらく、思考実験により、この拡張機能の謎が少し解けるでしょう。
複数のパターン ケースで定義された関数はすべて 1 つの場所に配置する必要があるという制限を削除したとしましょう。これfoo ("bar", Nothing) = ...
により、モジュールの先頭に記述して、他の場所と同じようにケースを作成できfoo ("baz", Just x) = ...
ます。実際、さらに進んで、異なるモジュールでケースを完全に定義できるようにしましょう!
使い方がわかりにくく、エラーが発生しやすいと思われる場合は、その通りです。
ある程度の正気を取り戻すために、いくつかの制限を加えることができます。たとえば (ha, ha) の場合、次のプロパティを保持する必要があります。
- このような関数が使用される場所では、指定された引数は1 つのパターンに正確に一致する必要があります。それ以外はコンパイラ エラーです。
- 新しいパターンを追加しても (別のモジュールをインポートすることを含む)、有効なコードの意味が変わることはありません。同じパターンが選択されるか、コンパイラ エラーが発生します。
True
orのような単純なコンストラクターのマッチングが簡単であることは明らかですNothing
。また、物事を少しハンドウェーブして、コンパイラーがリテラルのあいまいさを解消できると仮定することもでき"bar"
ます"baz"
。
一方で、引数を like のようなパターンにバインドするのは扱いにくくなります。そのようなパターンを書くということは、あいまいさが生じるため、likeやそれ以降の(x, Just y)
パターンを書く能力を放棄することを意味します。さらに悪いことに、パターン ガードは非常に一般的な一致を必要とするため、ほとんど役に立たなくなります。多くの一般的なイディオムは、際限のないあいまいさの頭痛の種になります。もちろん、「デフォルト」のフォールスルー パターンを記述することは完全に不可能です。(True, _)
(False, Just "foobar")
これは、型クラス インスタンスの場合とほぼ同じ状況です。
必要なプロパティを次のように緩和することで、表現力を取り戻すことができます。
- このような関数が使用される場所では、少なくとも 1 つのパターンに一致する必要があります。一致しない場合はコンパイラ エラーです。
- 複数のパターンが一致する関数が使用されている場合は、最も具体的なパターンが使用されます。一意で最も具体的なパターンがない場合は、エラーが発生します。
- 関数が一般的なインスタンスに一致する方法で使用されているが、より具体的なインスタンスに一致する引数に実行時に適用される可能性がある場合、これはコンパイラ エラーです。
モジュールをインポートするだけで、新しいより具体的なパターンをスコープに持ち込むことで、関数の動作を変更できる状況にあることに注意してください。高階関数を含む複雑なケースでは、状況が曖昧になることもあります。それでも、多くの場合、問題が発生する可能性は低いです。たとえば、ライブラリで一般的なフォールスルー パターンを定義し、必要に応じてクライアント コードに特定のケースを追加させます。
それは大まかOverlappingInstances
にあなたを置く場所です。上記の例で示唆されているように、新しいオーバーラップを作成することが常に不可能であるか望ましい場合であり、異なるモジュールが異なる競合するインスタンスを表示しない場合は、おそらく問題ありません。
実際のところ、are によって取り除かれた制限はOverlappingInstances
、可能なインスタンスは後で追加できるという「オープン ワールド」の前提の下で型クラスを使用して賢明に動作させるためにあるということです。これらの要件を緩和することで、その負担を自分で負うことになります。そのため、新しいインスタンスを追加する方法をすべて検討し、それらのシナリオのいずれかが重大な問題であるかどうかを検討してください。あいまいで悪意のあるコーナーケースでも何も壊れないと確信している場合は、先に進んで拡張機能を使用してください.
ほとんどの人は、型指向の推論ではなく、制約指向の推論を望んでいるため、重複するインスタンスを要求します。型クラスは型指向推論用に作成されたものであり、Haskell は制約指向推論に対する洗練されたソリューションを提供していません。
ただし、newtypes を使用することで「優れた機能をカプセル化」することはできます。インスタンスが重複しやすい次のインスタンス定義があるとします。
instance (SomeConstraint a) => SomeClass a where ...
代わりに以下を使用できます。
newtype N a = N { unN :: a }
instance (SomeConstraint a) => SomeClass (N a) where ...
現在、Haskell の型クラス システムには、個々の型N a
ごとに無償で照合するのではなく、照合する適切な特定の型 (つまり ) があります。これにより、インスタンスのスコープを制御できます。これは、N
newtype にラップされたものだけが一致するようになるためです。
OverlappingInstances
型クラスレベルでは実装できない多くの便利なものを書くことができますが、これらの大部分は、単一の関数従属性を使用するように再編成できます(ここでは種類の多態的なスタイルで書かれています)
class TypeEq (a :: k) (b :: k) (t :: Bool) | a b -> t where
typeEq :: Proxy a -> Proxy b -> HBool t
これは現在、(完全に一般的な方法で)。を使用してのみ実装できますOverlappingInstance
。ユースケースの例には、OlegによるOOPのHaskellへのエンコードが含まれます。したがって、私の良い使い方の1つの例はOverlappingInstances
、古典的なHListペーパーからのこの実装です。TypeEq
この特定の機能は、コンパイラーのサポートによって非常に簡単に提供でき(さらに、fundepレベルではなくtype関数で機能する)、したがって、単一のモジュールをどこかにTypeEqで固定することは、私にとってそれほど悪くはないようです。
私が危険な型クラスのハッカーに従事しているとき、私はしばしばそのIncoherentInstances
振る舞い(最初に一致するインスタンスを選ぶ)が推論しやすく、より柔軟であることに気付くので、少なくとも設計の探索段階でそれを使用します。私が望むことをする何かを手に入れたら、私は拡張機能を取り除こうとします。特に、動作の悪いもの(これらのようなもの)に注意を払います。