【问题标题】:Linking/Combining Type Classes in Haskell在 Haskell 中链接/组合类型类
【发布时间】:2011-02-22 01:37:38
【问题描述】:

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

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

【问题讨论】:

  • preturn :: a -> p b 是错字吗?

标签: haskell monads typeclass


【解决方案1】:

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

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

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

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

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

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

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

我仍然不真正推荐无缘无故地做这种事情,但这并不像我说的那么糟糕。过失。

【讨论】:

  • 谢谢!我很好奇我是否只是在复杂的思考中遗漏了一些明显的东西,或者这真的搞砸了。不用说,我坚持“好”的答案。
  • @thegravian:一个明智的决定。如果它有帮助,你的想法本质上并不是荒谬的,考虑到 Haskell 的类型类系统,它只是不可行。我认为已经提出了一些可以使其工作的扩展,但到目前为止还没有实现。
  • @camcann:解决方案并不是真正的那么可怕,是吗?我的意思是,“undecidable”这个词有点吓人,但是 Haskell 类型检查已经完成了指数级的时间,所以我不会让它阻止我做我真正想做的事情......
  • @camccann:我找不到痛苦。在我看来,它只是一个普通的实例声明。像往常一样在类型检查器中进行 Prolog。我错过了什么?
  • @Norman Ramsey:这是一个相当无趣的事实,除非你的意思是“......没有启用UndecidableInstances”,在这种情况下我既害怕又好奇。在任何地方都有详细信息的链接吗?我有大约六行(我认为是有效的 Haskell 98)代码,它们导致 GHC 占用 100% CPU 并占用数十 GB 内存,直到我终止进程,但这只是 实用 非终止,远没有那么有趣。
【解决方案2】:

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

【讨论】:

  • 你说得对,设计不可行(而且真的有点毫无意义)。我决定避免这样做,但仍然很好奇 Haskell 是否有某种设施可以做到这一点,而我很想念它们。它可能不会,因为这是正常人不会做的事情。
  • 类似的情况是mtl's 和transformers's MonadTrans 类。它们完全一样。在这个简单的深奥示例中如何统一类(如果可能的话)的答案对于实际代码也非常有用。
  • @yairchu:公平地说,Don 的“删除一个类”的解决方案也是该案例的正确解决方案,即删除mtl...
  • @camccann:现在一起使用不同的 monad 转换器仍然很有用。 Haskell 的类型类的一大优点是它们是开放的(不像 Java 的接口,您不能将实例添加到 int)。能够合并和调整类型类将使它们更加开放。例如,假设某人实现了一个“Monad”类,但他没有让它扩展Functor,因为其他人发现他可能应该这样做;调整/合并类事后分析的能力在那时非常有用。
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 这不起作用,因为您已经重叠了Monad 的所有其他实例。但是在尝试破坏一些东西之后,GHC 似乎比我想象的单参数类型类更聪明。显然,您只会得到一个像这样的通用实例,但作为一次性实例,考虑到OverlappingInstances,这可以正常工作。
【解决方案4】:

另一种解决方案是使用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

【讨论】:

  • Control.Arrow 中找不到WrapMonad,但在instance ArrowApply a => Monad (a ()) 中找不到。 newtype Kleisli m a b = Kleisli (a -> m b); instance Monad m => ArrowApply (Kleisli m);通过这些实例进行往返,您最终不会回到与您开始时相同的ArrowApplypre a x y = a x y, post a x y = x -> a () y.
【解决方案5】:

虽然这没什么意义,但试试

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

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

【讨论】:

  • 不过,这与他的要求相反。当然instance (PhantomMonad m) => Monad m where ...会带来更多的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-09
相关资源
最近更新 更多