Haskellの型チェッカーは妥当です。問題は、あなたが使用しているライブラリの作者が何かをしたということです...あまり合理的ではありません。
簡単な答えは次のとおりです。はい、10 :: (Float, Float)
インスタンスがあれば完全に有効ですNum (Float, Float)
。コンパイラーまたは言語の観点から、それについて「非常に間違っている」ことは何もありません。それは、数値リテラルが何をするかについての私たちの直感とは関係ありません。型エラーに慣れているので、型システムに慣れているので、当然のことながら、驚いて失望します。
Num
インスタンスとfromInteger
問題
コンパイラーが受け入れること10 :: Coord
、つまり10 :: (Float, Float)
。のような数値リテラル10
は「数値」型を持っていると推測されると仮定するのは理にかなっています。箱のうち、数値リテラルがあると解釈することができInt
、Integer
、Float
、またはDouble
。他のコンテキストがない数値のタプルは、これらの4つのタイプが数値である点で、数値のようには見えません。私たちは話をしていませんComplex
。
しかし幸いにも、残念ながら、Haskellは非常に柔軟な言語です。規格では、のような整数リテラルは型10
として解釈されると規定してfromInteger 10
いますNum a => a
。したがって、インスタンスが作成さ10
れたすべての型として推測できますNum
。これについては、別の回答でもう少し詳しく説明します。
あなたの質問を投稿する場合ので、経験豊富なHaskellerはすぐにすることを発見するために10 :: (Float, Float)
受け入れられるためには、そこのようなインスタンスでなければなりませんNum a => Num (a, a)
かNum (Float, Float)
。にはそのようなインスタンスはないPrelude
ため、別の場所で定義されている必要があります。を使用する:i Num
と、gloss
パッケージがどこから来たのかすぐにわかりました。
同義語と孤立インスタンスを入力する
しかし、ちょっと待ってください。gloss
この例ではタイプを使用していません。なぜインスタンスはgloss
あなたに影響を与えたのですか?答えは2つのステップで来ます。
まず、キーワードで導入された型の同義語type
は、新しい型を作成しません。モジュールでは、書き込みCoord
は単にの省略形です(Float, Float)
。同様にGraphics.Gloss.Data.Point
、Point
はを意味し(Float, Float)
ます。言い換えれば、あなたCoord
とgloss
のはPoint
、文字通り同等です。
したがって、gloss
メンテナが書くことを選択したときinstance Num Point where ...
、彼らはあなたのCoord
タイプをのインスタンスにしましたNum
。それはと同等ですinstance Num (Float, Float) where ...
かinstance Num Coord where ...
。
(デフォルトでは、Haskellは型シノニムは、クラスのインスタンスにすることはできません。gloss
著者は、言語拡張のペアを可能にするために、持っていたTypeSynonymInstances
とFlexibleInstances
、インスタンスを書くこと。)
第二に、孤立したインスタンス、つまりとのinstance C A
両方が他のモジュールで定義されているインスタンス宣言であるため、これは驚くべきことです。ここでは、関係する各部分(つまり、、、および)がに由来し、どこにでも存在する可能性が高いため、これは特に油断できません。C
A
Num
(,)
Float
Prelude
あなたの期待は、Num
で定義されていることPrelude
、そしてタプルとFloat
で定義されているPrelude
ため、これらの3つの機能の動作に関するすべてがで定義されていPrelude
ます。完全に異なるモジュールをインポートすると何かが変わるのはなぜですか?理想的にはそうではありませんが、孤立したインスタンスはその直感を壊します。
(GHCは孤立したインスタンスについて警告することに注意してくださいgloss
。その警告の作成者は特にその警告を上書きしました。これにより、警告が発せられ、少なくともドキュメントに警告が表示されます。)
クラスインスタンスはグローバルであり、非表示にできません
さらに、クラスインスタンスはグローバルです。モジュールから推移的にインポートされたモジュールで定義されたインスタンスは、コンテキスト内にあり、インスタンス解決時にタイプチェッカーで使用できます。これにより、グローバルな推論が便利になります。これは、(通常)のようなクラス関数(+)
は、特定の型に対して常に同じであると想定できるためです。しかし、それはまた、地域の決定が世界に影響を与えることも意味します。クラスインスタンスを定義すると、下流のコードのコンテキストが完全に変更され、モジュール境界の背後にマスクしたり隠したりすることはできません。
インポートリストを使用してインスタンスのインポートを回避することはできません。同様に、定義したモジュールからインスタンスをエクスポートすることは避けられません。
これは、Haskell言語設計の問題の多い、議論の多い領域です。このredditスレッドには、関連する問題についての興味深い議論があります。たとえば、インスタンスの可視性の制御を許可することに関するエドワードクメットのコメントを参照してください。
(ちなみに、この回答が示したように、孤立したインスタンスを使用することで、いくつかの点でグローバルインスタンスの仮定を破ることができます!)
何をすべきか—ライブラリの実装者向け
実装する前によく考えてくださいNum
。fromInteger
問題を回避することはできません—いいえ、定義fromInteger = error "not implemented"
しても問題は改善されません。整数リテラルがインスタンス化している型であると誤って推測された場合、ユーザーは混乱したり驚いたりしますか?特にハッキングする必要がある場合、提供(*)
と(+)
その重要性はありますか?
Conal Elliott vector-space
(種類の種類の場合*
)またはEdward Kmett linear
(種類の種類の場合)などのライブラリで定義された代替算術演算子の使用を検討してください* -> *
。これは私が自分で行う傾向があることです。
を使用し-Wall
ます。孤立したインスタンスを実装しないでください。孤立したインスタンスの警告を無効にしないでください。
または、linear
他の多くの正常に動作するライブラリの先導に従い、末尾が.OrphanInstances
またはである別のモジュールで孤立したインスタンスを提供します.Instances
。また、そのモジュールを他のモジュールからインポートしないでください。その後、ユーザーは必要に応じて孤立を明示的にインポートできます。
孤児を定義していることに気付いた場合は、可能で適切であれば、代わりに上流のメンテナにそれらの実装を依頼することを検討してください。私は、孤立したインスタンスをShow a => Show (Identity a)
、それが追加されるまで頻繁に作成していましたtransformers
。私はそれについてバグ報告をしたかもしれません。覚えていません。
何をすべきか—図書館利用者向け
多くのオプションはありません。丁寧かつ建設的に!-図書館の管理者に連絡してください。彼らにこの質問を指摘してください。彼らは問題のある孤児を書く特別な理由があったかもしれませんし、気付かないかもしれません。
より広く:この可能性に注意してください。これは、Haskellの真のグローバルな影響がある数少ない領域の1つです。インポートするすべてのモジュール、およびそれらのモジュールがインポートするすべてのモジュールが孤立インスタンスを実装していないことを確認する必要があります。型注釈は問題を警告することがあります:i
。もちろん、GHCiでチェックすることもできます。
十分に重要な場合newtype
は、type
類義語ではなく独自のを定義します。あなたはだれもそれらを台無しにしないだろうとかなり確信することができます。
オープンソースのライブラリから派生した問題が頻繁に発生する場合は、もちろん独自のバージョンのライブラリを作成できますが、メンテナンスがすぐに頭痛の種になる可能性があります。