如何扩展 Haskell 类型的实例?

How to extend a Haskell type instance?

This 优秀的教程,定义了一个 YesNo 类型 class 和一个 YesNo Int 实例如下:

class YesNo a where
   yesno :: a -> Bool

instance YesNo Int where
   yesno 0 = False
   yesno _ = True

这适用于像这样的显式类型声明:

*Main> yesno (0::Int)
False

我想避免显式类型声明并使其与 Num a class 约束一起工作:

instance (Num a) => YesNo a where
   yesno 0 = False
   yesno _ = True

但是这个实例定义没有编译错误: Constraint is no smaller than the instance head

我知道 Num 的构造函数数量比 YesNo class 多,因此出现错误。但是如何在不设置 UndecidableInstances 编译器标志的情况下解决这个问题?

正如 chi 已经指出的那样,-XUndecidableInstances 并不是什么大问题 – 好吧,它比 -XFlexibleInstances 更具侵入性;每当需要时,您都应该想一想 – 但它非常 安全 。它永远不会成功编译无法按预期方式工作的代码。

也就是说,要实现让 yesno 0 独立工作的目标,instance (Num a) -> YesNo a 不是 正确的做法。有了那个实例,数字 0 应该具有什么具体类型仍然是完全不明确的(Num a => a 而不是 一个具体类型)。因此编译器必须使用 defaulting 并且,默认的 Num 类型是 Integer。因此,要使其正常工作,声明该实例就足够了:

instance YesNo Integer where
   yesno = (/=0)

或者,如果您希望计算 总是Int 中完成,您可以使用等式约束来实现:

instance (a ~ Int) => YesNo a where
   yesno = (/=0)

虽然这个确实需要 -XUndecidableInstances(加上完全无害的 -XFlexibleInstances-XGADTs-XTypeFamilies 中的任何一个,以启用等式约束)。

None 这很有意义 无论如何 :类型 class 的要点是为实现看起来 [=38= 的类型创建实例]不同。如果你将其限制为数字类型,这就是你对这些实例所做的,那么你最好远离类型 classes 并只定义

yesno :: (Num n, Eq n) => n -> Bool
yesno = (/=0)

从这个意义上说:不可判定的实例有点代码味道;在写之前确保你真的 需要一个 class