2017-10-10 25 views
2

私は現在haskellにサーバーを構築しており、その言語の初心者として、新しいアプローチのzu Monadの構成を試してみたいと思います。アイデアは、私たちが根底にあるモナドを知らずに働くハスケルの型システムと戦わずにモナドを抽象化する方法は?

isGetRequest :: (SupportsRequests m r) => m Bool 
    isGetRequest = do 
     method <- liftRequests $ requestMethod 
     return $ method == GET 

    class (Monad m, RequestSupport r) => SupportsRequests m r | m -> r where 
     liftRequests :: r a -> m a 

    class (Monad r) => RequestSupport r where 
     requestMethod :: r Method 

のようなライブラリのメソッドを書くことができるということです。もちろん、この例では、isGetRequestを(RequestSupport r)モナドで直接操作するだけで十分でしたが、私のライブラリはモナドに対して複数の制約を持つ可能性があります。しかし、私は同じモジュール内の異なる懸念のすべてを実装したくないし、別のモジュール(孤立したインスタンス)に分散させたくありません。 そのため、m個のモナドはSupports*クラスのみを実装し、本当の懸念事項を他のモナドに委譲しています。

上記のコードは完全に機能するはずです(GHCのいくつかの言語拡張で)。私はエラーを取得する

class (Monad m, CRUDSupport c a) => SupportsCRUD m c a | m a -> c where 
    liftCRUD :: c x -> m x 

class (Monad c) => CRUDSupport c a | c -> a where 
    list :: c [a] -- List all entities of type a 

ません:型チェッカーがaパラメータことを好きではないよう

Could not deduce (SupportsCRUD m c a0) from the context [...] 
The type variable 'a0' is ambiguous [...] 
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 
When checking the class method: liftCRUD [...] 

は思え残念ながら、私はCRUD(削除読むアップデートを作成します)懸念を持ついくつかの問題を得ましたリフトCRUDの署名に直接発生しません。 aは関数の依存関係から派生することができないため、これは理解できます。

私の脳の型チェッカーでは、AllowAmbiguousTypesを使用して、後でタイプaを推測することは問題ではないことを教えています。CRUDに関するいくつかのメソッドがライブラリメソッドで実行されています。残念ながら、GHCは

bookAvailable :: (SupportsCRUD m c Book) => m Bool 
bookAvailable = do 
    books <- liftCRUD (list :: c [Book]) -- I use ScopedTypeVariables 
    case books of 
     [] -> return False 
     _ -> return True 

利回り

Could not deduce (SupportsCRUD m c0 a1) arising from a use of 'liftCRUD' [...] 
The type variables c0, a1 are ambiguous [...] 

例えば、この推論ステップを行うことができないようだ私がまだコンパイラについて推論することができませんようです。私はこの問題を解決する方法がありますか?あるいは、コンパイラが何を推測できるのかを理解するための少なくとも1つの方法はありますか?また、あなたはforallとスコープになりたい変数をバインドする必要がありScopedTypeVariables使用するには

よろしく、 bloxx

+3

'SupportsCRUD'クラスの宣言には、3つの型変数、' m'、 'c'、' a'があります。あなたの例で、これらの3つの変数についてコンパイラが推測する値が何であるか、そしてその理由を教えてください。 –

+1

あなたはあなたの資金で 'm a - > c'で何をしましたか?つまり、「cの選択は、型チェッカーが「m」と「a」だけに基づいてインスタンスを選択できるという効果を持つ特定の型「m」と「a」によって一意的に決定されます。文脈で、 'c'はあなたがインスタンスヘッドに置いたものに従います。 – jberryman

+0

@jberryman実際には、モナドmと "CRUDエンティティ" aに対して、型チェッカーは一意のモナドc(これはCRUD操作をサポートするもの)を選択できることをコンパイラに伝えるべきです。 – bloxx

答えて

3

。だから、私はコンパイルするコードを取得するために(私はあなたの質問に入るのタイプミスだったと仮定し、私が作ったいくつかの些細な修正後の)必要があったことすべてだった

bookAvailable :: forall m c. (SupportsCRUD m c Book) => m Bool 
... 

でなければなりません。

+0

うわー!そうですね、それで私はとても驚いています。今、私は質問があります。なぜ、GHCは、「liftCRUD」のタイプを推測するときに「a〜Book」を選ぶのですか?明らかにその選択肢には、適切なインスタンスがあります。しかしあとで明らかにすると、私にはあいまいな悲鳴を上げる良い選択となる別のインスタンスを追加することもできます。 –

+2

ああ、私の主張の証言をしようとしたあと、「明らかに後で別のインスタンスを追加することができる」という証拠を作った後、おそらく理由が分かると思います。すべての 'SupportsCRUD'インスタンスは対応する' CRUDSupport'インスタンスを意味します。 –

+0

@ダニエルワーグナーええ、それは私もあまりにもうまくいった。私は 'CRUDSupport'が' SupportsCRUD'のスーパークラスであることを忘れていました。なぜなら、それが後で宣言されたからです。そして、スーパークラスが最初に来るのに慣れています。 – luqui

関連する問題