web-dev-qa-db-ja.com

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

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

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

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

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

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

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

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

82
Dan Dyer

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

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

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

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

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

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

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

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

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

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

89
Yitz

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

あなたは良い仲間です。 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)、使用するインスタンスを選択できます。
  • いずれの場合も、インスタンスが自動的に選択された場合でも、環境では、簡単なインターフェイス(ハイパーリンクまたはホバーインターフェイスなど)を使用して、使用されたインスタンスを簡単に確認できます。

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

42
yairchu

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

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

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

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

36
augustss

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

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

17
sclv

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

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

5

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

3
mxc