2016-02-20 8 views
10

非常に多くのハードコードされたインスタンスを必要としません....そして私は、私は不必要な何かをやったような気がします。、それは本当に私は長い時間モナド変換子のユーザー、初めてモナド変換子のライターをしています

私たちは複数のDBテーブルを持つプロジェクトに取り組んでおり、異なるモナド・スタックにセットをハードコーディングするのは扱いにくくなっていたので、プラグイン可能なモナド・トランスフォーマーに分割して、レベルは、このように

doSomething::(HasUserTable m, HasProductTable m)=>Int->m String 

(HasXTableはコンクリートモナド変圧器です)。これらの独立したモナド・トランスは完全にモジュラー形式で挿入または削除でき、DBハンドルを格納し、ResourceTなどを必要とします。

私の最初の試みは、保持するのに使用されるReaderTをラップすることでしたDBハンドル。 ReaderT(およびStateTなど)は、ハードコードされた "リフト"のチェーンを使用せずに積み重ねることができず、スタック要素のプラガブルなモジュール性が破られるため、これがうまくいかないことがすぐに分かりました。

唯一の解決策は、ReaderTモナドのコピーを完全に別々に書くことであり、それぞれがより低いレベルの他のものへのアクセスを可能にするということでした。これは動作しますが、解決策は、定型的なコードで満たされ、これはさらに悪くなりますこれは何

class HasUserTable m where 
    getUser::String->m User 

newtype UserTableT m r = UserTableT{runUserTableT::String->m r} 

--Standard monad instance stuff, biolerplate copy of ReaderT 
instance Functor m=>Functor (UserTableT m) where.... 
instance Applicative m=>Applicative (UserTableT m) where.... 
instance Monad m=>Monad (UserTableT m) where.... 
instance Monad m=>HasUserTable (UserTableT m) where.... 

--Gotta hardcode passthrough rules to every other monad transformer 
--in the world, mostly using "lift".... 
instance MonadTrans BlockCacheT where.... 
instance (HasUserTable m, Monad m)=>HasUserTable (StateT a m).... 
instance (HasUserTable m, Monad m)=>HasUserTable (ResourceT m).... 
.... etc for all other monad transformers 

--Similarly, need to hardcode passthrough rules for all other monads 
--through the newly created one 
instance MonadResource m=>MonadResource (UserTableT m) where.... 
instance MonadState a m=>MonadState a (UserTableT m) where.... 
instance (MonadBaseControl IO m) => MonadBaseControl IO (UserTableT m).... 
.... etc for all other monad transformers 

のようなものは、我々が追加それぞれの新しいモナド変換(IE-それぞれの新しいテーブルのためにさらに多くのパススルールールを追加する必要があるということです私たちは、他のすべてのテーブルのモナド変圧器をパススルーする必要性を追加し、私たちは、n^2インスタンス宣言を必要とする!)

これを行うにはクリーンな方法はありますか?

+1

これは、拡張可能な影響を非常に思い起こさせるように見えます。 https://hackage.haskell.org/package/free-vlには実装と、それを説明する論文への参照があります。 –

+5

"n^2インスタンス宣言が必要なので"これはモンタージュトランスフォームのmtlスタイルに関するよく知られた問題です。あなたの型を 'ReaderT String m r'と書く代わりに、一般化されたnewtypeの導出を使って、読者のものと同じインスタンスを導き出すことができます(ここではほとんどの場合そうです)。ほとんどのインスタンスを 'MonadTrans t、HasUserTable m => HasUserTable(t m)'で置き換えることはできますが、この種の型推論は強制終了し、いくつかの拡張が必要です。 – user2407038

+0

@ user2407038一般化された 'MonadTrans t、HasUserTable m => HasUserTable(t m)'を使用する際の問題は、UserTableTにも適用され、書く必要がある適切なインスタンスと競合していました。私はこれがなぜn^2問題が存在するのではないかと考えている(さもなければ、彼らはすべてのモナド変圧器に対してこれを行っただろう)。私はn^2問題に対するあなたのコメントは、私の質問に対する答えかもしれないと思います。しかし、幸せではありません....あなたはモナド変圧器、そしてたぶんハスケルでも何もできません。私はこの問題について議論している参考書を持っています。私はそれを答えとして受け入れます。 – jamshidh

答えて

8

これは、モナド変圧器の問題の1つです。新しい変圧器を追加する場合、ますます多くの定型文例を書き込む必要があります。毎回nのインスタンスであり、合計でO(n^2)インスタンスです。たとえば、このスケーリングの問題in the mtl source codeを見ることができます。モナド変圧器は容易に拡張できません。

ここでは、私たちが毎日使っているモナドの割合は、mtlで提供されているトランスの組み合わせとして表現できます。つまり、他の誰かがすでに退屈なインスタンスをすべて書き込んでいます。しかし、これらの変圧器は確かにすべてののモナドをカバーしていないので、自分で作成する必要があるときはいつもかまいません。

これは、タイピングを効果的に行うための新しいアプローチを考案するために継続的に取り組まれている理由です。 Haskellの良い例は、フリーモナドに基づいて、入力を代入する代数的アプローチをとるKiselyovなどのextensible-effects libraryです。このライブラリの設計は、という2つの記事に記載されています。これはmtlのアプローチの問題を記述するのに時間を費やし、More Extensible Effectsはライブラリの更新され最適化された実装について説明しています。

安全で拡張可能なエフェクトタイピングがどれだけ可能かを確認したい場合は、Idwin言語のEdwin Bradyのeffects libraryを参照してください。 effectstutorial、元のProgramming and Reasoning with Algebraic Effectsの記事、Resource-dependent Algebraic Effectsの説明には、effectsという新しい機能が説明されています。おそらく、私がこのリストで忘れてしまったいくつかのリソースがあります。

+0

説明と拡張可能なエフェクトへのポインタをありがとう。私は間違いなく拡張可能なエフェクトを見ていきますが、ここではこの問題を解決すると考えていますが、複数の人がn^2の問題が現実であり、ハスケルコミュニティで進行中の研究問題であると指摘しています。 FWIW、私が考えているのは、これが解決すべき非常に重要な問題です(拡張性が既に解決されていない限り)、おそらく私がHaskellで唯一の大きな問題の一つになっているのです。 – jamshidh

+1

まあ、いつものように、それはトレードオフです。モナド変圧器は確かに「拡張性効果」よりも簡単です(さらに簡単なのはまだあなたのものです)。実際には、柔軟性で支払う価格は実際には急ではありません。エフェクトタイピングはオプションです。ほとんどの言語は、ハスケルのようにそれを強制するのではなく、50年もの間、業界として驚くほど生産的でした。 –

関連する問題