次のように、グラフのノードとエッジを表す独自のデータ型があります。
data Node a = Node a deriving (Show, Eq) data Label a = Label a deriving (Show) data Cost = CostI Int | CostF Float deriving (Show) data Edge label node = Edge (Label label, (Node node,Node node), Cost) deriving (Show)
ここで、エッジに 2 つのノードが含まれているかどうかを確認する関数を次のように作成します。
isEdge:: (Eq n) => (Edge l n) -> (Node n, Node n) -> Bool isEdge (Edge (_, (n1,n2), _)) (n3, n4) = result where result = (n1 == n3) && (n2 == n4)
関数はうまく機能します。ここでの問題は、関数から (Eq n) を削除すると失敗することです。
Node
では、上記の宣言では Eq クラスから派生していると宣言しているのに、なぜでしょうか?data Node a = Node a deriving (Show, Eq)
2 に答える
Eq
GHC が導出するインスタンスはNode a
次のようなものです:
instance Eq a => Eq (Node a) where
(Node x) == (Node y) = x == y
(Node x) /= (Node y) = x /= y
でコンパイルすると、生成されたコードを表示できます-ddump-deriv
。制約はEq a
明らかな理由で必要です。つまり、関数は比較できないため、GHC はたとえばEq
forのインスタンスを推論できませんでした。Node (a -> b)
Eq
しかし、GHC がfor Node a
for some のインスタンスを推論できないという事実は、等値型ではないa
型の値を構築することを止めるという意味ではありません。Node a
a
人々が比較不可能な s を構築するのを止めたい場合はNode
、次のような制約を設定してみてください。
data Eq a => Node a = Node a deriving (Eq, Show)
しかし今、GHC はコンパイラプラグマが必要だと教えてくれます:
Illegal datatype context (use -XDatatypeContexts): Eq a =>
OK、それをファイルの先頭に追加しましょう:
{-# LANGUAGE DatatypeContexts #-}
コンパイルします:
/tmp/foo.hs:1:41: Warning: -XDatatypeContexts is deprecated: It was widely
considered a misfeature, and has been removed from the Haskell language.
問題は、s を使用するすべての関数にクラス制約が必要になることです。これは面倒です (関数にはまだ制約が必要です!)。(また、ユーザーが非等価型を使用して を作成したいが、等価性をテストしない場合、何が問題になるのでしょうか?)Node
Eq
Node
ただし、実際には GHC にやりたいことをさせる方法があります: Generalized Algebraic Data Types ( GADTs ):
{-# LANGUAGE GADTs, StandaloneDeriving #-}
data Node a where
Node :: Eq a => a -> Node a
これは元の定義と同じように見えますが、Node
値コンストラクター(以前はデータ宣言の右側にあったもの) が単なる関数であり、制約を追加できることが強調されています。これで、GHC はNode
s に入れることができるのは等値型だけであることを認識し、以前に試みた解決策とは異なり、制約を必要としない新しい関数を作成できます。
fromNode :: Node a -> a
fromNode (Node x) = x
Eq
インスタンスとインスタンスを派生させることはできShow
ますが、構文が少し異なります。
deriving instance Eq (Node a)
deriving instance Show (Node a)
(したがって、上記の StandaloneDeriving プラグマです。)
これが機能するために、GHC では GADT に制約を追加する必要もありShow
ます (生成されたコードをもう一度見ると、制約がなくなっていることがわかります)。
data Node a where
Node :: (Eq a, Show a) => a -> Node a
これで、GHC が推論できるので、Eq
制約 offを取り除くことができます!isEdge
(これは、このような単純な状況では間違いなくやり過ぎです。繰り返しになりますが、内部に関数を含むノードを構築したい場合、そうすべきではありませんか?しかし、GADT は、かなり似たような状況で、ノードの特定のプロパティを強制したい場合に非常に役立ちます。データ型.クールな例を参照してください)。
EDIT(未来から):書くこともできます
data Node a = (Eq a, Show a) => Node a
ただし、GADT 拡張機能を有効にして、インスタンスを個別に派生させる必要があります。このスレッドを参照してください。
句をデータ宣言に追加するderiving
と、派生句には、宣言のスコープ内の型変数に必要な制約が含まれます。この場合、deriving Eq
は基本的に次のインスタンスを作成します。
instance Eq a => Eq (Node a) where
(Node a) == (Node b) = a == b
(Node a) /= (Node b) = a /= b
派生Eq
インスタンスは、データ コンストラクターの右側にある型の Eq インスタンスに依存します。
Eq
これは、インスタンスを自動的に派生させる方法が他にないためです。2 つの値が同じ型を持ち、すべてのコンポーネントが等しい場合、これらの値は等しいと見なされます。したがって、コンポーネントが等しいかどうかをテストできる必要があります。Eq
ポリモーフィック コンポーネントの等価性を一般的にテストするには、インスタンスが必要です。
これは だけでEq
なく、すべての派生クラスに当てはまります。たとえば、このコード
toStr :: Edge l n -> String
toStr = show
制約を追加しないと機能しません(Show l, Show n)
。その制約がなければ、表示する関数はEdge
、内部のラベルとノードを表示するために何を呼び出すべきかわかりません。