10

次のように定義された 2 つの型クラスがあり、機能は同じですが名前が異なるとします。

class Monad m where
    (>>=)  :: m a -> (a -> m b) -> m b
    return :: a -> m a

class PhantomMonad p where
    pbind    :: p a -> (a -> p b) -> p b
    preturn  :: a -> p a

これら 2 つのクラスを結び付けて、PhantomMonad のインスタンスが自動的に Monad のインスタンスになるようにする方法はありますか?それとも、各クラスのインスタンスを明示的に記述する必要がありますか? どんな洞察でも大歓迎です、ありがとう!

4

5 に答える 5

13

良い答え:いいえ、あなたが望んでいることは実際には実行可能ではありません。希望どおりに動作するように見えるインスタンスを作成できます。プロセスでGHC拡張機能が必要になる可能性がありますが、希望どおりに機能しません。

賢明でない答え:恐ろしいタイプレベルのメタプログラミングを使用して、おそらくあなたが望むことを達成することができますが、それは複雑になる可能性があります。何らかの理由でこれを絶対に機能させる必要がない限り、これは実際には推奨されません。

公式には、インスタンスは他のインスタンスに実際に依存することはできません。GHCは決定を行うときに「インスタンスヘッド」のみを確認し、クラスの制約は「コンテキスト」にあるためです。ここで「型クラスの同義語」のようなものを作成するには、考えMonadられるすべての型のインスタンスのように見えるものを記述する必要がありますが、これは明らかに意味がありません。Monad独自の問題がある他のインスタンスと重複することになります。

それに加えて、そのようなインスタンスはインスタンス解決の終了チェック要件を満たさないと思います。そのため、UndecidableInstances拡張機能も必要になります。これは、GHCのタイプチェッカーを無限ループに送るインスタンスを作成する機能を意味します。 。

本当にそのうさぎの穴を掘り下げたいのなら、オレグ・キセリョフのウェブサイトを少し見て回ってください。彼はHaskellのタイプレベルのメタプログラミングの守護聖人のようなものです。

確かに楽しいことですが、コードを書いてそれを機能させたいだけなら、おそらく苦労する価値はありません。

編集:さて、後から考えると、私はここで問題を誇張しました。のようなものは1回限りで正常に機能し、 -およびGHC拡張機能PhantomMonadがあれば、必要なことを実行できます。複雑なことは、問題の内容よりもはるかに複雑なことをしたいときに始まります。ノーマン・ラムゼーに電話してくれたことに心から感謝します。私は本当にもっとよく知っているべきでした。OverlappingUndecidableInstances

私はまだ正当な理由なしにこの種のことをすることを本当にお勧めしません、しかしそれは私がそれを鳴らしたほど悪くはありません。Meaculpa。

于 2010-05-20T20:16:37.797 に答える
7

それは珍しいデザインです。PhantomMonadは他のクラスと同型であるため、単に削除することはできません。

于 2010-05-20T20:06:31.947 に答える
7

PhantomMonad のインスタンスが自動的に Monad のインスタンスになるように、これら 2 つのクラスを結び付ける方法はありますか?

FlexibleInstancesはい。ただし、少し驚くべき言語拡張機能とUndecidableInstances次のものが必要です。

instance (PhantomMonad m) => Monad m where
  return = preturn
  (>>=)  = pbind

FlexibleInstancesそれほど悪くはありませんが、決定不能のリスクは少し憂慮すべきものです。問題は、推論規則では何も小さくなっていないことです。そのため、このインスタンス宣言を別の同様の宣言 (逆方向など) と組み合わせると、型チェッカーが簡単に永遠にループする可能性があります。

私は一般的に を快適に使用していますが、正当な理由がなけれFlexibleInstancesば避ける傾向があります。UndecidableInstancesここでは、最初から使用した方がよいという Don Stewart の提案に同意しますMonad。しかし、あなたの質問は思考実験の性質上、答えは、オレグレベルの恐怖に陥ることなく、やりたいことができるということです.

于 2010-05-21T02:26:12.723 に答える
3

別の解決策は、 を使用することnewtypeです。これはまさにあなたが望むものではありませんが、そのような場合によく使用されます。

これにより、同じ構造を指定するさまざまな方法をリンクできます。たとえば、ArrowApply(Control.Arrow から) とMonadは同等です。を使用Kleisliして、モナドから ArrowApply を作成したり、ArrowApply からモナドArrowMonadを作成したりできます。

また、一方向のラッパーも可能です: WrapMonad(Control.Applicative で) モナドからアプリケーションを形成します。

class PhantomMonad p where
    pbind    :: p a -> (a -> p b) -> p b
    preturn  :: a -> p a

newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a }

newtype WrapReal m a = WrapReal { unWrapReal :: m a }

instance Monad m => PhantomMonad (WrapPhantom m) where
  pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f))
  preturn = WrapPhantom . return

instance PhantomMonad m => Monad (WrapReal m) where
  WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f))
  return = WrapReal . preturn
于 2010-05-21T09:01:44.183 に答える
1

これはあまり意味がありませんが、試してみてください

instance Monad m => PhantomMonad m where
    pbind = (>>=)
    preturn = return

(おそらく、いくつかのコンパイラ警告が非アクティブ化されています)。

于 2010-05-20T20:09:30.247 に答える