在Haskell中链接/组合类型类

时间:2010-05-20 19:52:37

标签: haskell monads typeclass

假设我有两个类型如下定义,它们在功能上相同但名称不同:

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

有没有办法将这两个类绑定在一起,所以作为PhantomMonad实例的东西会自动成为Monad的一个实例,或者每个类的实例都必须显式写入?任何见解都将非常感谢,谢谢!

5 个答案:

答案 0 :(得分:13)

答案很好:不,你希望做的事情真的不可行。你可以编写一个看起来像你想要的实例,可能在这个过程中需要一些GHC扩展,但它不会按照你想要的方式工作。

不明智的回答:您可以使用可怕的类型级元编程来完成您想要的任务,但它可能会变得复杂。除非绝对因某种原因需要这项工作,否则不建议这样做。

官方实例不能真正依赖于其他实例,因为GHC在做出决策时只查看“实例头”,而类约束在“上下文”中。要在此处创建类似“类型同义词”的内容,您必须为所有可能的类型编写看起来像Monad的实例,这显然没有意义。您将与Monad的其他实例重叠,这有其自身的问题。

最重要的是,我认为这样的实例不会满足实例解析的终止检查要求,因此您还需要UndecidableInstances扩展,这意味着能够编写实例,将GHC的类型检查器发送到无限循环中。

如果你真的想要去那个兔子洞,请稍微浏览Oleg Kiselyov's website;他是Haskell中类型级元编程的守护神。

确实如此,这很有趣,但如果你只是想编写代码并让它工作,可能不值得痛苦。

编辑:好的,事后我在这里夸大了问题。 PhantomMonad之类的内容可以作为一次性使用,并且应该根据Overlapping - 和UndecidableInstances GHC扩展来执行您想要的操作。当你想做任何比问题更复杂的事情时,复杂的东西就会启动。真诚地感谢诺曼拉姆齐给我打电话 - 我真的应该知道的更好。

我仍然没有真正推荐无缘无故地做这类事情,但这并不像我说的那么糟糕。 Mea culpa。

答案 1 :(得分:7)

这是一个不寻常的设计。你能不能只删除PhantomMonad,因为它与其他类同构。

答案 2 :(得分:7)

  

有没有办法将这两个类绑定在一起,所以作为PhantomMonad实例的东西会自动成为Monad的一个实例?

是的,但它需要稍微惊人的语言版本FlexibleInstancesUndecidableInstances

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

FlexibleInstances并不是那么糟糕,但不确定性的风险稍微令人担忧。问题是在推理规则中,没有什么东西变小,所以如果你将这个实例声明和另一个类似的声明组合起来(比如反方向),你可以很容易地让类型检查器永远循环。

我通常习惯使用FlexibleInstances,但我倾向于在没有充分理由的情况下避免使用UndecidableInstances。在这里,我同意Don Stewart的建议,即你最好先使用Monad。但是你的问题更多的是思想实验的本质,答案是你可以做你想做的事情,而不会陷入奥列格的恐惧程度。

答案 3 :(得分:3)

另一种解决方案是使用newtype。这不完全是你想要的,但在这种情况下经常使用。

这允许链接指定相同结构的不同方式。例如,ArrowApply(来自Control.Arrow)和Monad是等效的。您可以使用Kleisli从一个monad中创建一个ArrowApply,并使用ArrowMonad从ArrowApply中创建一个monad。

此外,单向包装器是可能的:WrapMonad(在Control.Applicative中)形成一个monad的应用程序。

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

答案 4 :(得分:1)

虽然这没有用,但试试

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

(可能已停用某些编译器警告)。