Haskellの孤立したインスタンス


86

Haskellアプリケーションを-Wallオプションでコンパイルすると、GHCは孤立したインスタンスについて文句を言います。例:

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

型クラスToSElemは私のものではなく、HStringTemplateによって定義されています

これを修正する方法(インスタンス宣言をResultが宣言されているモジュールに移動する)と、GHCが孤立したインスタンスを回避する理由を理解しましたが、それでも私の方法の方が優れていると思います。コンパイラが不便であるかどうかは気にしません-私よりもむしろ。

ToSElemPublisherモジュールでインスタンスを宣言する理由は、他のモジュールではなく、HStringTemplateに依存するのはPublisherモジュールだからです。私は関心の分離を維持し、すべてのモジュールがHStringTemplateに依存することを避けようとしています。

Haskellの型クラスの利点の1つは、たとえばJavaのインターフェイスと比較した場合、閉じているのではなく開いているため、インスタンスをデータ型と同じ場所で宣言する必要がないことだと思いました。GHCのアドバイスはこれを無視することのようです。

ですから、私が探しているのは、私の考えが健全であり、この警告を無視/抑制することで正当化されるという検証、または私のやり方に反対するより説得力のある議論のいずれかです。


回答とコメントでの議論は、実行可能ファイルで孤立したインスタンスを定義することと、他の人に公開されているライブラリで定義することには大きな違いがあることを示しています。この非常に人気のある質問は、孤立したインスタンスを定義するライブラリのエンドユーザーにとって、孤立したインスタンスがいかに混乱する可能性があるかを示しています。
クリスチャンコンクル2014年

回答:


94

なぜあなたがこれをしたいのか理解していますが、残念ながら、Haskellクラスがあなたの言うように「オープン」に見えるのは幻想にすぎないかもしれません。多くの人は、これを行う可能性は、以下で説明する理由から、Haskell仕様のバグであると感じています。とにかく、クラスが宣言されているモジュールまたは型が宣言されているモジュールのいずれかで宣言する必要があるインスタンスに本当に適切でない場合、それはおそらく、newtypeまたは他のラッパーを使用する必要があることを示していますあなたのタイプの周り。

孤立したインスタンスを回避する必要がある理由は、コンパイラの利便性よりもはるかに深くなります。他の回答からわかるように、このトピックはかなり物議を醸しています。議論のバランスをとるために、孤立したインスタンスを作成してはならないという観点を説明します。これは、経験豊富なHaskellerの多数意見だと思います。私自身の意見は途中ですが、最後に説明します。

この問題は、同じクラスとタイプに対して複数のインスタンス宣言が存在する場合、標準のHaskellにはどちらを使用するかを指定するメカニズムがないという事実に起因します。むしろ、プログラムはコンパイラーによって拒否されます。

その最も単純な効果は、他の誰かがモジュールの依存関係から遠く離れて行った変更のために、突然コンパイルを停止する完全に機能するプログラムを持つことができるということです。

さらに悪いことに、遠方の変更が原因で、動作中のプログラムが実行時にクラッシュし始める可能性があります。特定のインスタンス宣言からのものであると想定しているメソッドを使用している可能性があり、プログラムが不可解にクラッシュし始めるのに十分なだけ異なる別のインスタンスにサイレントに置き換えられる可能性があります。

これらの問題が発生しないことを保証したい人は、誰かが、どこでも、特定のタイプの特定のクラスのインスタンスを宣言したことがある場合、作成されたプログラムで他のインスタンスを再度宣言してはならないという規則に従う必要があります誰でも。もちろん、を使用newtypeして新しいインスタンスを宣言する回避策はありますが、それは常に少なくとも小さな不便であり、時には大きな不便です。したがって、この意味で、孤立したインスタンスを意図的に作成する人は、かなり失礼です。

では、この問題について何をすべきでしょうか?アンチオーファンインスタンスキャンプは、GHC警告はバグであり、オーファンインスタンスを宣言する試みを拒否するエラーである必要があると述べています。その間、私たちは自己規律を行使し、絶対にそれらを避けなければなりません。

あなたが見てきたように、それらの潜在的な問題についてそれほど心配していない人たちがいます。彼らは実際に、あなたが示唆するように、関心の分離のためのツールとして孤立したインスタンスの使用を奨励し、問題がないことをケースバイケースで確認する必要があると言っています。私は、他の人々の孤児の実例によって、この態度があまりにも騎士的であると確信するのに十分な回数不便を感じてきました。

正しい解決策は、インスタンスのインポートを制御するHaskellのインポートメカニズムに拡張機能を追加することだと思います。それは問題を完全に解決するわけではありませんが、世界にすでに存在する孤立したインスタンスからの損傷からプログラムを保護するのに役立つでしょう。そして、時間が経つにつれて、特定の限られたケースでは、おそらく孤立したインスタンスはそれほど悪くないかもしれないと確信するかもしれません。(そして、その誘惑が、孤児対策キャンプの一部が私の提案に反対している理由です。)

これらすべてからの私の結論は、少なくとも当面は、他の理由がない場合は他の人に配慮するために、孤立したインスタンスを宣言することは避けることを強くお勧めします。を使用しnewtypeます。


4
特に、これは図書館の成長に伴う問題になりつつあります。Haskellに2200を超えるライブラリがあり、数万の個別モジュールがあるため、インスタンスを取得するリスクは劇的に増大します。
ドンスチュワート

16
Re:「正しい解決策は、インスタンスのインポートを制御するHaskellのインポートメカニズムに拡張機能を追加することだと思います」このアイデアが誰かに興味がある場合は、例としてScala言語を調べる価値があるかもしれません。型クラスインスタンスのように使用できる「暗黙的」のスコープを制御するための、このような機能があります。
マット

5
私のソフトウェアはライブラリではなくアプリケーションであるため、他の開発者に問題を引き起こす可能性はほとんどありません。Publisherモジュールをアプリケーションと見なし、残りのモジュールをライブラリと見なすことができますが、ライブラリを配布する場合、Publisherがないため、孤立したインスタンスがありません。しかし、インスタンスを他のモジュールに移動すると、ライブラリはHStringTemplateへの不要な依存関係とともに出荷されます。したがって、この場合、孤児は大丈夫だと思いますが、別の状況で同じ問題が発生した場合は、アドバイスに耳を傾けます。
ダン・ダイアー

1
それは合理的なアプローチのように聞こえます。その場合に注意する必要があるのは、インポートするモジュールの作成者がこのインスタンスを新しいバージョンで追加するかどうかだけです。そのインスタンスが自分のインスタンスと同じである場合は、独自のインスタンス宣言を削除する必要があります。そのインスタンスが自分のインスタンスと異なる場合は、型の周りにnewtypeラッパーを配置する必要があります。これは、コードの大幅なリファクタリングになる可能性があります。
Yitz 2010年

@Matt:確かに、驚くべきことに、ScalaはHaskellがそうではないのにこれを正しく取得します!(もちろん、Scalaには型クラスの機械のファーストクラスの構文がありません。これはさらに悪いことです...)
Erik Kaplun 2015年

44

先に進んで、この警告を抑制してください!

あなたは良い仲間です。Conalは「TypeCompose」でそれを行います。「chp-mtl」と「chp-transformers」がそれを行い、「control-monad-exception-mtl」と「control-monad-exception-monadsfd」がそれを行います。

ところで、あなたはおそらくすでにこれを知っていますが、検索であなたの質問を知らず、つまずく人のために:

{-# OPTIONS_GHC -fno-warn-orphans #-}

編集:

私は、イッツが彼の答えの中で実際の問題として言及した問題を認めます。ただし、孤立したインスタンスを使用しないことも問題であると考えており、孤立したインスタンスを慎重に使用するのが難しい「すべての悪の中で最も少ない」を選択しようとしています。

あなたの質問はあなたがすでに問題をよく知っていることを示しているので、私は私の短い答えで感嘆符だけを使用しました。そうでなければ、私はあまり熱心ではなかっただろう:)

少し気晴らしですが、私が信じているのは、妥協のない完璧な世界での完璧なソリューションです。

Yitzが言及している問題(どのインスタンスが選択されているかわからない)は、次のような「全体的な」プログラミングシステムで解決できると思います。

  • あなたは単なるテキストファイルを原始的に編集しているのではなく、むしろ環境によって支援されています(たとえば、コード補完は関連するタイプのものだけを提案するなど)
  • 「低レベル」言語は型クラスを特別にサポートしておらず、代わりに関数テーブルが明示的に渡されます
  • しかし、「高水準」プログラミング環境は、Haskellが現在提示されているのと同じようにコードを表示し(通常、関数テーブルが渡されることはありません)、明白な場合は明示的な型クラスを選択します(たとえば、Functorのすべてのケースには1つの選択肢しかありません)、いくつかの例(zip listApplicativeまたはlist-monadApplicative、First / Last / liftおそらくMonoid)がある場合は、使用するインスタンスを選択できます。
  • いずれの場合も、インスタンスが自動的に選択された場合でも、環境では、簡単なインターフェイス(ハイパーリンクまたはホバーインターフェイスなど)を使用して、使用されたインスタンスを簡単に確認できます。

ファンタジーの世界(またはできれば未来)から戻って、今:孤立したインスタンスを「本当に必要な」ときに使用しながら回避することをお勧めします。


5
はい、しかし間違いなく、これらの出来事のそれぞれは、ある順序の間違いです。control-monad-exception-mtlとmonads-fdのEitherの悪いインスタンスが思い浮かびます。これらの各モジュールが独自の型を定義したり、新しい型のラッパーを提供したりすることを余儀なくされた場合は、それほど目立たなくなります。ほとんどすべての孤立したインスタンスは、発生するのを待っている頭痛の種であり、他に何もなければ、それがインポートされているかどうかを確認するために絶え間ない警戒が必要です。
エドワードKMETT 2010年

2
ありがとう。私はこの特定の状況でそれらを使用すると思いますが、Yitzのお​​かげで、それらがどのような問題を引き起こす可能性があるかをよりよく理解できるようになりました。
ダンダイアー2010年

37

孤立したインスタンスは厄介ですが、私の意見では、それらが必要になる場合があります。タイプが1つのライブラリに由来し、クラスが別のライブラリに由来するライブラリを組み合わせることがよくあります。もちろん、これらのライブラリの作成者は、考えられるすべてのタイプとクラスの組み合わせのインスタンスを提供することを期待することはできません。だから私はそれらを提供しなければなりません、そしてそれで彼らは孤児です。

インスタンスを提供する必要があるときに型を新しい型でラップする必要があるという考えは、理論的にはメリットのある考えですが、多くの状況では面倒です。これは、生活のためにHaskellコードを書かない人々によって提唱された種類のアイデアです。:)

したがって、先に進んで孤立したインスタンスを提供してください。彼らは無害です。
孤立したインスタンスでghcをクラッシュさせることができる場合、それはバグであり、そのように報告する必要があります。(ghcが複数のインスタンスを検出しないことに関して持っていた/持っていたバグは修正するのがそれほど難しくありません。)

ただし、将来、他の誰かが既に持っているインスタンスを追加する可能性があり、(コンパイル時)エラーが発生する可能性があることに注意してください。


2
良い例は(Ord k, Arbitrary k, Arbitrary v) ⇒ Arbitrary (Map k v)、QuickCheckを使用する場合です。
Erik Kaplun 2015年

17

この場合、孤立したインスタンスの使用は問題ないと思います。私の一般的な経験則は、型クラスを「所有」する場合、またはデータ型(またはその一部のコンポーネント)を「所有」する場合にインスタンスを定義できます。つまり、MyDataのインスタンスも問題ありません。少なくとも時々)。これらの制約の中で、インスタンスを配置することを決定するのはあなた自身のビジネスです。

もう1つの例外があります。型クラスもデータ型も所有していないが、ライブラリではなくバイナリを生成している場合は、それでも問題ありません。


5

(私はパーティーに遅れていることを知っていますが、これはまだ他の人に役立つかもしれません)

孤立したインスタンスを独自のモジュールに保持することができます。その場合、誰かがそのモジュールをインポートする場合、それは特にそれらが必要であるためであり、問​​題が発生した場合はインポートを回避できます。


3

これらの線に沿って、私はアンチオーファンインスタンスキャンプの位置WRTライブラリを理解していますが、実行可能ターゲットの場合、オーファンインスタンスは問題ないはずですか?


3
他人に失礼であるという点で、あなたは正しいです。ただし、依存関係チェーンのどこかで同じインスタンスが将来定義されると、将来の潜在的な問題が発生する可能性があります。したがって、この場合、リスクに見合う価値があるかどうかを判断するのはあなた次第です。
Yitz 2010年

5
実行可能ファイルに孤立したインスタンスを実装するほとんどすべての場合、それはあなたがすでにあなたのために定義されていることを望むギャップを埋めることです。したがって、インスタンスがアップストリームに表示される場合、結果として生じるコンパイルエラーは、インスタンスの宣言を削除できることを通知するための単なる有用なシグナルです。
ベン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.