2013-08-26 13 views
9

私は特殊な数値データ処理ライブラリを開発していますが、修正方法を理解できないというエラーが出てきました。最初に例を示してから問題を説明する方が簡単だと思います。私も奇妙な名前のために謝罪する、私は法的な目的のために難読化しなければならない。multiparam型クラスのあいまいなインスタンスを解決する

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances  #-} 

data MyError = MyError String deriving (Eq, Show) 

data MyList = MyList [Double] deriving (Eq, Show) 
data NamedList = NamedList String MyList deriving (Eq, Show) 

class MyNum a b ret where 
    myAdd  :: a -> b -> Either MyError ret 
    myLessThan :: a -> b -> Either MyError Bool 

instance MyNum MyList Double MyList where 
    myAdd (MyList xs) x = Right $ MyList $ map (+x) xs 
    myLessThan (MyList xs) x = Right $ all (< x) xs 

instance MyNum NamedList Double NamedList where 
    myAdd (NamedList n l) x = fmap (NamedList n) $ myAdd l x 
    myLessThan (NamedList n l) x = myLessThan l x 

私はこれをコンパイルしようとした場合、コンパイラはMyListに使用するMyNumの特定のインスタンスを把握することはできませんので、私はエラー

No instance for (MyNum MyList Double ret0) 
    arising from a use of `myLessThan' 
The type variable `ret0' is ambiguous 
Possible fix: add a type signature that fixes these type variable(s) 
Note: there is a potential instance available: 
    instance MyNum MyList Double MyList 
    -- Defined at testing_instances.hs:13:10 
Possible fix: 
    add an instance declaration for (MyNum MyList Double ret0) 
In the expression: myLessThan l x 
In an equation for `myLessThan': 
    myLessThan (NamedList n l) x = myLessThan l x 
In the instance declaration for `MyNum NamedList Double NamedList' 

を取得します。 MyNumの戻り値の型は簡単に導出されるため、myAddで動作しますが、myLessThanの場合はそれを把握することができません。このタイプキャストを使用して、全体的にきめ細かなエラー処理を簡単に追加できるようにしたいと思います。実際のコードには+、 - 、*、/、等価のものがありますので<、< =、>、> = MyNum Double MyList MyListMyNum MyList MyList MyListなどのインスタンスをNamedListとして作成します。これを行うより簡単な方法がない限り、多形交換可能演算子を持つことができるようになります。

ただし、使用するインスタンスがわかるように、2番目のインスタンスにmyLessThanに追加するタイプシグネチャを特定できません。 1つの解決策は算術演算子と比較演算子を2つの別々の型クラスに分割することですが、可能ならばそれを避けたいと思います。

答えて

11

functional dependenciesを使用すると、「ret」がabで一意に決まることを指定できます。

... 
{-# LANGUAGE FunctionalDependencies #-} 
... 
class MyNum a b ret | a b -> ret where 
... 

これは、型チェッカーは、それはあなたの内の引数からのみabを知って、正しいインスタンスの定義を選ぶことができることを知っています:

myLessThan (NamedList n l) x = myLessThan l x 

追加を定義した場合、コンパイラは、今文句を言うだろう同じab異なるret付きインスタンス、など

instance MyNum MyList Double SomeOtherType where 
+0

これは私のためのハスケルで以前はよく知られていない構造であり、まさに私が必要としていたものです。ありがとう! – bheklilr

+2

@bheklilrうれしいです。また、いくつかの方法で作業しやすく、より強力なタイプファミリーについて学ぶこともできます。 – jberryman

4

jberrymanが指摘したように、TypeFamiliesを使用できます。そして、これはどのようにある:

-{-# LANGUAGE FlexibleInstances  #-} 
+{-# LANGUAGE TypeFamilies #-} 

-class MyNum a b ret where 
- myAdd  :: a -> b -> Either MyError ret 
+class MyNum a b where 
+ type Ret a b 
+ myAdd  :: a -> b -> Either MyError (Ret a b) 

-instance MyNum MyList Double MyList where 
+instance MyNum MyList Double where 
+ type Ret MyList Double = MyList 

-instance MyNum NamedList Double NamedList where 
+instance MyNum NamedList Double where 
+ type Ret NamedList Double = NamedList 

私はクラス・パラメータからassociated typeRetretタイプを移動しました。
クラスパラメータabRetの機能があると述べるのは、TypeFamilyです。

+0

私はそれが 'MyNum'クラスから3番目の明示的な型パラメータを削除するのが好きです。しかし、誰が優れたパフォーマンスを示すのかについての情報はありますか?より速く動くことを意味するならば、もっと混乱したコードを気にしないでください。もしタイプファミリーがより効率的ならば、私はむしろその解決策を使用したいと思います。 – bheklilr

+1

私の知る限り、どちらのソリューションも同等のパフォーマンスを発揮します。コンパイルされたコアを調べて違いがあるかどうかを確認できますが、この場合は1つあるとは思いません。 – ocharles

+0

@ocharles私はチャンスを得たときにそれをプロファイリングするつもりです。私は結果を後で更新して投稿しようとします – bheklilr

関連する問題