7

タイプファミリーで式を表現しようとしていますが、私が望む制約を記述する方法を見つけられないようで、それができないと感じ始めています。ここに私のコードです:タイプ制約を持つHas​​kellタイプのファミリーインスタンス

class Evaluable c where 
    type Return c :: * 
    evaluate :: c -> Return c 

data Negate n = Negate n 

instance (Evaluable n, Return n ~ Int) => Evaluable (Negate n) where 
    type Return (Negate n) = Return n 
    evaluate (Negate n) = negate (evaluate n) 

これはすべてコンパイルされますが、正確に私が望むものを表現していません。 Negateインスタンスの制約では、Evaluableの表現では、Negate内の式の戻り値の型は、(Return n ~ Int)でなければならないので、私はそれにネゲートを呼び出すことができますが、それはあまりにも制限的です。戻り値の型は実際にはNum型クラスのインスタンスである必要があります。このクラスのクラスはnegateです。そのようにしてDoubles、Integers、またはNumの他のインスタンスも否定することができ、ただIntではありません。 Numは型クラスであるとReturn nがタイプですので、しかし、私はちょうど代わり

Return n ~ Num 

を書き込むことはできません。 Return nは型ではない型変数であるので、私はまた、代わりに

Num (Return n) 

を置くことはできません。

私はハスケルでも可能なことをしようとしていますか?もしそうでなければ、それはすべきか、あるいは私はそれの背後にあるいくつかの理論を誤解していますか?私はJavaのような制約を加えることができるように感じる。この質問がもっと分かりやすいかどうか私に教えてください。

編集:ありがとうございました。返信は助けになり、私が疑ったものになっています。型チェッカーはUndecidableInstancesなしで何をしたいのかを処理できないようですので、私の質問は、私が本当に決めることができないものですか?それはHaskellのコンパイラですが、それは一般的ですか?つまり、より高度な型チェッカに決定可能な「戻り値nがNumのインスタンスであることを確認する」という意味の制約が存在する可能性がありますか?

+0

btw、GHCは、 'FlexibleContexts'または何か、試行錯誤の過程で?私はそれが本当に確信しているからです - ちょうどサイドノートは_ "これはHaskellでも可能ですか?" –

答えて

6

実は、あなたが言及した正確に何を行うことができます。

{-# LANGUAGE TypeFamilies, FlexibleContexts, UndecidableInstances #-} 

class Evaluable c where 
    type Return c :: * 
    evaluate :: c -> Return c 

data Negate n = Negate n 

instance (Evaluable n, Num (Return n)) => Evaluable (Negate n) where 
    type Return (Negate n) = Return n 
    evaluate (Negate n) = negate (evaluate n) 

Return nは確かにちょうどInt缶のようなクラスのインスタンスである可能タイプです。あなたの混乱は、制約の議論になる可能性があります。答えは「正しい種類のもの」です。 Intの種類はReturn nのように*です。 Numは種類が* -> Constraintなので、のいずれか種類の*が引数になります。 Num (a :: *)が合法であるのと同じ方法で、Num Intを制約として書くことは完全に合法ですが(真空ですが)。

+0

そうですね、私はすでにこれを見てきたが、FlexibleContextsとUndecidableInstancesの拡張によって私は緊張していると説明していたはずです。私が間違っていない限り、彼らはもっと多くのインスタンスを追加するときに彼らが最後に私をかむために戻ってくることができるように感じる。 – user3773003

+4

私が知っている限り、 'FlexibleContexts'は完全に安全で、単純に非型変数を型パラメータとして許可します。 'UndecidableInstances'は、インスタンスがループを形成する場合(コンパイラがこれをチェックできないので)、コンパイル中に非終了を引き起こす可能性があります。詳細については、[この回答](http://stackoverflow.com/a/5017549/925978)を参照してください。 – crockeea

+7

@ user3773003:私はエリックよりも強いことを言いたいと思います: 'FlexibleContexts 'なしで' TypeFamilies'を使うのは妥当ではないと思います。おそらく 'UndecidableInstances'がなければ!柔軟でないコンテキストルールは、タイプファミリのない言語に対して設計されています。彼らが拡張を必要とするのは驚きではありません。また、型ファミリは型レベルの関数なので、(型レベルの)終了チェッカが途方もなく入り込むことは驚くことではありません。値のレベルで終了チェッカーを持っていないので、 'UndecidableInstances'は同様に表現型のレベルを取得します。 –

2

、エリックの答えを補完するために、私は一つの可能​​な選択肢を提案してみましょう:代わりにタイプファミリーの機能的依存関係を使用して:

class EvaluableFD r c | c -> r where 
    evaluate :: c -> r 

data Negate n = Negate n 

instance (EvaluableFD r n, Num r) => EvaluableFD r (Negate n) where 
    evaluate (Negate n) = negate (evaluate n) 

これは、それが少し簡単に結果の型についての話になり、私は思います。

type GivesInt = EvaluableFD Int 

あなたがしてこれを行うことができます:たとえば、あなたはまた、(私は面白いに見えるために、引数を置く理由である)、部分的にこれを適用するためにConstraintKindsを使用することができます

foo :: EvaluableFD Int a => Negate a -> Int 
foo x = evaluate x + 12 

を書くことができますあなたのクラスも同様ですが、もっと迷惑でしょう:

type GivesInt x = (Evaluable x, Result x ~ Int) 
+0

はい、もう一度私は元の質問で試したことを言及すべきでした。これもUndecidableInstancesと、私が避けたいと考えるFlexibleInstancesが必要です。 – user3773003

+0

@ user3773003、私はあまりにも楽観的ではないので、あなたが望むものをこれらの拡張機能なしで得ることができます。もちろん、何かが欠けているかもしれません。 – dfeuer

+0

@ user3773003文字通り 'FlexibleInstances'には欠点はなく、' UndecidableInstances'も効果的に無害です。 –

関連する問題