在Haskell中链接/组合类型类

the*_*ian 10 monads haskell 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
Run Code Online (Sandbox Code Playgroud)

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

C. *_*ann 13

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

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

正式实例不能真正依赖于其他实例,因为GHC只在决策时查看"实例头",而类约束在"上下文"中.为了让像一个"类型类同义词"在这里,你必须写什么样子的一个实例Monad所有可能的类型,这显然是没有意义的.您将与其他Monad具有自身问题的实例重叠.

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

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

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

编辑:好的,事后我在这里夸大了问题.PhantomMonad考虑到Overlapping- 和UndecidableInstancesGHC扩展,类似的东西可以作为一次性工作,并且应该做你想要的.当你想做任何比问题更复杂的事情时,复杂的东西就会启动.真诚地感谢诺曼拉姆齐给我打电话 - 我真的应该知道的更好.

我仍然没有真正推荐没有充分理由做这种事情,但它并没有我听起来那么糟糕.Mea culpa.


Don*_*art 7

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

  • 类似的情况是`mtl`和`变形金刚'的'MonadTrans`类.他们完全一样.在这个简单的深奥的例子中,如何统一类(如果可能的话)的答案也可能对真正的代码非常有用. (3认同)
  • @camccann:现在使用不同的monad变换器仍然很有用.Haskell类型类的一个好处是它们是开放的(不像Java的接口,你不能在`int`中添加一个实例).能够合并和调整类型类会使它们更加开放.比方说,有人实施了一个"Monad"课程,并没有让它扩展到"Functor",就像其他人发现他应该有的那样; 事后调整/合并课程的能力非常有用. (3认同)
  • @yairchu:公平地说,Don的"删除一个类"的解决方案也是该案例的正确解决方案,即删除`mtl` ... (2认同)

Nor*_*sey 7

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

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

instance (PhantomMonad m) => Monad m where
  return = preturn
  (>>=)  = pbind
Run Code Online (Sandbox Code Playgroud)

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

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