mtlの様々なバージョン 2009年1月現在,MonadCatchIO-mtl以外にもmtlから派生した様々なバージョンがHackageDBで公開されています。こうした派生系の狙いは大きく二つあります。一つは,様々な歴史的事情を引きずったmtlを新たにデザインすることです。もう一つは,GHC 6.10から正式に加わった「型族(Type Families)」を関数従属性の代わりに利用することです(参考リンク)。 第6回で説明したように,多引数型クラスをまともに扱うには,多くの場合,型変数の間の制約を決める必要があります。mtlではこの制約を示すのに関数従属性を使っていました。一方,mtl-tfやmonads-tfでは,型族を使うように定義を書き換えています。 今回は型族について詳しく説明するスペースはないので,実例だけを示しましょう。mtlのMonadStateクラスは,「型変数mが何か」によって型変数sの具体的な型が求められるという定義になっています。 class (Monad m) => MonadState s m | m -> s where get :: m s put :: s -> m () instance MonadState s (State s) where get = State $ \s -> (s, s) put s = State $ \_ -> ((), s) 一方,mtl-tfやmonads-tfでは,「型関数(Type Function)」であるStateTypeの適用によって型が決定されるという定義になっています(参考リンク)。 mtl-tf: type family StateType (m :: * -> *) ~ 略 ~ class (Monad m) => MonadState m where get :: m (StateType m) put :: StateType m -> m () type instance StateType (State s) = s instance MonadState (State s) where get = State $ \s -> (s, s) put s = State $ \_ -> ((), s) ~ 略 ~ type instance StateType (StateT s m) = s instance (Monad m) => MonadState (StateT s m) where ~ 略 ~ type instance StateType (ListT m) = StateType m instance (MonadState m) => MonadState (ListT m) where get = lift get put = lift . put type instance StateType (ErrorT e m) = StateType m instance (Error e, MonadState m) => MonadState (ErrorT e m) where ~ 略 ~ monads-tf: class (Monad m) => MonadState m where type StateType m get :: m (StateType m) put :: StateType m -> m () ~ 略 ~ instance (Monad m) => MonadState (Lazy.StateT s m) where type StateType (Lazy.StateT s m) = s get = Lazy.get put = Lazy.put instance (Monad m) => MonadState (Strict.StateT s m) where type StateType (Strict.StateT s m) = s get = Strict.get put = Strict.put ~ 略 ~ instance (Error e, MonadState m) => MonadState (ErrorT e m) where type StateType (ErrorT e m) = StateType m get = lift get put = lift . put instance (MonadState m) => MonadState (ListT m) where type StateType (ListT m) = StateType m ~ 略 ~ mtl-tfとmonads-tfの定義は一見,異なるものに見えるかもしれません。しかし,よく見比べてみれば,構文糖衣を利用しているに過ぎないことがわかります。両者の定義は全く同じです。mtl-tfでのtype instance宣言も,monads-tfでのインスタンス宣言中のtype宣言も,「=」を使ったStateTypeの関数定義になっています。この定義を使って,getメソッドの型「m (StateType m)」やputメソッドの型「StateType m -> m ()」がより具体的な型に展開されます。 もう一つ,MonadErrorクラスに対する定義を見てみましょう。MonadErrorクラスに対して以下のような制約が書かれています。 mtl: class (Monad m) => MonadError e m | m -> e where -- | Is used within a monadic computation to begin exception processing. throwError :: e -> m a ~ 略 ~ catchError :: m a -> (e -> m a) -> m a instance MonadError IOError IO where ~ 略 ~ instance (Error e) => MonadError e (Either e) where ~ 略 ~ instance (Monad m, Error e) => MonadError e (ErrorT e m) where ~ 略 ~ mtl-tf: type family ErrorType (m :: * -> *) ~ 略 ~ class (Monad m) => MonadError m where -- | Is used within a monadic computation to begin exception processing. ~ 略 ~ catchError :: (Error (ErrorType m)) => m a -> (ErrorType m -> m a) -> m a type instance ErrorType IO = IOError instance MonadError IO where ~ 略 ~ type instance ErrorType (Either e) = e instance (Error e) => MonadError (Either e) where ~ 略 ~ type instance ErrorType (ErrorT e m) = e instance (Monad m, Error e) => MonadError (ErrorT e m) where ~ 略 ~ bmonads-tf: class (Monad m) => MonadError m where type ErrorType m ~ 略 ~ throwError :: ErrorType m -> m a ~ 略 ~ catchError :: m a -> (ErrorType m -> m a) -> m a instance MonadError IO where type ErrorType IO = IOError throwError = ioError catchError = catch ~ 略 ~ instance (Error e) => MonadError (Either e) where type ErrorType (Either e) = e ~ 略 ~ instance (Monad m, Error e) => MonadError (ErrorT e m) where type ErrorType (ErrorT e m) = e throwError = ErrorT.throwError catchError = ErrorT.catchError ~ 略 ~ いずれも「モナドがIOの場合にはIOErrorをエラーの型とする。それ以外の場合には,Errorクラスのインスタンスであるということ以外の制約を設けない」という定義になっています。 ここまでで,mtlを型族で再定義したのがmtl-tfとmonads-tfだということはわかりました。では,mtl-tfとmonads-tfの違いは何でしょうか? mtl-tfとmonads-tfの最大の違いは,monads-tfはより大きな範囲でmtlを再構成していることです。monads-tfでは「関数従属性を使うか,型族を使うか,それともそうした拡張機能を一切持っていないか」という違いによってライブラリの開発や利用を左右されないよう,基本の型や関数をtransformersというパッケージに別途切り分けています。そして,その上に作られた型クラスインタフェースの関数従属版をmonads-fd,型族版をmonads-tfとして提供しています(参考リンク)。 またtransformersでは,Applicativeなどmtlがまだサポートしてないbaseパッケージのいくつかのクラスをすでにサポートしています。 こうしたことから考えると,そう遠くない将来,mtlからより柔軟かつ高機能なmonads-*への切り替えが起こるかもしれませんね。 |
著者紹介 shelarcy 今回の説明はいかがでしたか? 様々な事情から,現在のエラー・モナドはmtlの中でもとりわけ制限が多く,比較的扱いづらいものになっています。 しかし,そうした制限の多くは歴史的な事情によって作られたものであり,本質的なものではありません。将来的にはこうした非本質的な制限は解消されるでしょう。MonadCatchIO-mtlのように,やりようによってはこうした制限を回避することもできます。そうしたことがきちんと伝わっていれば幸いです。 |