假设我有两个类型如下定义,它们在功能上相同但名称不同:
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的一个实例,或者每个类的实例都必须显式写入?任何见解都将非常感谢,谢谢!
答案 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的一个实例?
是的,但它需要稍微惊人的语言版本FlexibleInstances
和UndecidableInstances
:
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
(可能已停用某些编译器警告)。