如果你违反了单子法则,你会发生啥?
Posted
技术标签:
【中文标题】如果你违反了单子法则,你会发生啥?【英文标题】:What happens to you if you break the monad laws?如果你违反了单子法则,你会发生什么? 【发布时间】:2011-09-17 23:36:53 【问题描述】:编译器或库中更“原生”的部分(IO 或可以访问黑魔法和实现的函数)是否对这些定律做出假设?打破它们会导致不可能的事情发生吗?
或者它们只是表达了一种编程模式——即,唯一会因为破坏它们而惹恼的人是那些使用你的代码并且没想到你这么粗心的人?
【问题讨论】:
Haskell 警察逮捕了你 :-) 【参考方案1】:对于使用更多“主流”语言的人来说,这就像实现一个接口,但这样做不正确。例如,假设您正在使用一个提供 IShape 接口的框架,并且您实现了它。但是,您对 draw() 方法的实现根本不绘制,而只是实例化了您的类的 1000 多个实例。
框架会尝试使用您的 IShape 并用它做一些合理的事情,天知道会发生什么。这将是一种有趣的火车残骸观看。
如果你说你是 Monad,你就是在“声明”你遵守它的合同和法律。其他代码会相信你的声明并采取相应的行动。既然你撒了谎,事情就会以意想不到的方式出错。
【讨论】:
【参考方案2】:monad 法则只是期望实例遵循的附加规则,超出了类型系统中可以表达的规则。就Monad
表达一种编程模式而言,规则是该模式的一部分。此类规则也适用于其他类型类:Monoid
与 Monad
具有非常相似的规则,并且通常预计 Eq
的实例将遵循对等式关系的预期规则,以及其他示例。
因为这些法则在某种意义上是类型类的“一部分”,所以其他代码期望它们会遵守并采取相应行动应该是合理的。因此,行为不端的实例可能会违反客户端代码逻辑所做的假设,从而导致错误,而错误的责任应该归咎于实例,而不是使用它的代码。
简而言之,“打破单子法则”通常应该被理解为“编写有缺陷的代码”。
我将通过一个涉及另一种类型类的示例来说明这一点,该类型类是根据 Daniel Fischer 在 haskell-cafe 邮件列表中给出的类进行修改的。 (希望)众所周知,标准库包含一些行为不端的实例,即浮点类型的 Eq
和 Ord
。正如您可能猜到的那样,当涉及到 NaN 时,就会发生不当行为。考虑以下数据结构:
> let x = fromList [0, -1, 0/0, -5, -6, -3] :: Set Float
0/0
产生一个 NaN,这违反了 Data.Set.Set
对 Ord
实例所做的假设。这个Set
是否包含0
?
> member 0 x
True
是的,当然可以,它就在眼前!现在,我们在Set
中插入一个值:
> let x' = insert (0/0) x
这个Set
仍然包含0
,对吧?毕竟,我们没有删除任何东西。
> member 0 x'
False
...哦。哦,亲爱的。
【讨论】:
对不起,我只能接受一个答案——这是一个非常酷的例子。谢谢你。 很好的例子!这也表明 Haskell 应该有更完整的类型类范围。如果有一个偏序类,Float 就会有一个这样的实例,而不是被塞进 Ord。然后 Set 实现者可能会想到使用二叉树列表,这样他们的实现就可以在偏序上工作。 @Sjoerd Visscher:实际上,我认为我们真正想要的是一个“无意义的排序”类型的类。在许多情况下,我们不关心Set
使用什么排序,只要它是一致的总排序即可。在排序中任意给 NaN 一个语义上无意义的位置将使Set
按原样工作,而偏序类型类将在浮点数上提供正确的语义排序。根据 IEEE 规范,目前的情况既没有提供正确的任意排序,也没有提供正确的比较实现。
公平地说,您确实尝试插入一个值x
,使得x /= x
。如果我们将 Eq
类解释为给出 PER 而不是等价关系,那么 0/0
是一个不属于该类型的“语义”域的值,如果你使用它,所有的赌注都会被取消。至少这就是我试图合理化这种行为的方式。
@Russell O'Connor: Eq
更容易合理化,尽管它显然打算是一种等价关系;此外,通过示例列表进行相等的线性搜索也可以正常工作。更大的问题是Ord
,它甚至在 Haskell 报告中被指定为一个总订单,这也是导致Data.Set.Set
中错误行为的原因。请注意compare (0/0) (0/0)
= GT
。如上所述,我的偏好是拥有Eq
和Ord
的部分、全部和非语义全部版本。【参考方案3】:
编译器不会对法律做出任何假设,但是,如果您的实例不遵守法律,它的行为就不会像 monad 那样——它会做一些奇怪的事情,否则在您的用户看来无法正常工作(例如删除值,或以错误的顺序评估事物)。
此外,假设 monad 定律成立,您的用户可能进行的重构显然是不合理的。
【讨论】:
另一个考虑因素是,许多用于处理 monad 的组合子只有在假设某些 monad 法则的子集时才是唯一的。 @Edward -- 你所说的独特是什么意思?不是每个函数应用都会给出唯一的结果吗? 欧文,是的,但从某种意义上说,许多函数都具有规范的含义,仅考虑到所使用的规律和类型——不管实现的选择如何。 liftM 和 fmap 必须根据 monad 法则做同样的事情,甚至不用看他们的身体。失去这些,它不能保证是一个有效的默认定义。 Daniel:给定类型和的规律。对于执行不同操作的实例,您不能同时遵守 monad 和 functor 定律。 liftMWrong 意味着如果将其用作 fmap 或 'fmap f xs == xs >>= return ,则“fmap id = id law”被破坏。如果不这样做,f' 就会损坏。 不过,向 GHC 教授一些单子定律并不是特别难。 -# RULES "return/bind" forall f x 。例如, return x >>= f = f x #- 可能很有用。也许它应该在标准库中。以上是关于如果你违反了单子法则,你会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章