10

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

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 的实例,或者每个类的实例都必须显式编写?任何见解将不胜感激,谢谢!

4

5 回答 5

13

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

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

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

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

如果您真的想深入那个兔子洞,请浏览一下Oleg Kiselyov 的网站;他是 Haskell 中类型级元编程的守护神。

可以肯定的是,这很有趣,但如果你只是想编写代码并让它工作,可能不值得那么痛苦。

编辑:好的,事后看来,我在这里夸大了这个问题。考虑到- 和GHC 扩展,类似的东西PhantomMonad一次性工作得很好,应该做你想做的事。当你想做比问题更复杂的事情时,复杂的事情就开始了。我真诚地感谢 Norman Ramsey 给我打电话——我真的应该知道得更好。OverlappingUndecidableInstances

我仍然不建议在没有充分理由的情况下做这种事情,但这并不像我说的那么糟糕。过失。

于 2010-05-20T20:16:37.797 回答
7

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

于 2010-05-20T20:06:31.947 回答
7

有没有办法将这两个类联系在一起,这样 PhantomMonad 的实例就会自动成为 Monad 的实例?

是的,但它需要稍微令人担忧的语言扩展FlexibleInstancesUndecidableInstances

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

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

我通常很喜欢使用FlexibleInstances,但我倾向于在UndecidableInstances没有充分理由的情况下避免使用。在这里,我同意唐斯图尔特的建议,即你最好从Monad一开始就使用。但是你的问题更多的是思想实验的性质,答案是你可以做你想做的事而不会陷入奥列格的恐惧水平。

于 2010-05-21T02:26:12.723 回答
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
于 2010-05-21T09:01:44.183 回答
1

虽然这并没有真正的意义,尝试

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

(可能会停用一些编译器警告)。

于 2010-05-20T20:09:30.247 回答