如何限制Haskell中的开放世界假设

时间:2016-08-30 07:12:24

标签: haskell typeclass

为了提高我对GHC扩展的了解,我决定尝试用单位来实现数字,而我想要做的一件事是使用数字文字来表示无单位值。但由于Haskell所做的开放世界假设,结果证明不太实际。我无法工作的最小例子如下:

data Unit u a = Unit a

data NoUnit

instance Num a => Num (Unit NoUnit a) where
    -- (+) (*) (-) abs signum not important
    fromInteger = Unit . fromInteger

type family Multiply a b where
    Multiply NoUnit NoUnit = NoUnit

multiply :: Num a => Unit u1 a -> Unit u2 a -> Unit (Multiply u1 u2) a
multiply (Unit a) (Unit b) = Unit $ a * b

现在,如果我尝试做multiply 1 1之类的事情,我希望结果值是明确的。因为获得Num (Unit u a)类型的唯一方法是将u设置为NoUnit。其余的a应由违约规则处理。

不幸的是,由于Haskell的开放世界假设,有些邪恶的人可能会认为即使是具有单位的数字应该是有效的Num实例,即使这样的事情会违反(*) :: a -> a -> a数字与单位的乘法不适合该类型的签名。

现在开放世界的假设不是一个不合理的假设,特别是因为Haskell报告没有禁止孤立实例。但在这种特定情况下,我确实想告诉GHC,Num Unit实例唯一有效的幻像单元类型是NoUnit

有没有办法明确说明这一点,并且在一些附注中会禁止孤儿实例允许GHC放宽开放世界的假设?

当尝试使用部分依赖键入来使我的程序更安全时,这种事情已经出现过几次。每当我想为基本案例指定NumIsStringIsList实例,然后使用自定义值或函数来获取所有其他可能的案例。

1 个答案:

答案 0 :(得分:14)

你无法关闭开放世界的假设,但有一些方法可以限制它,包括这个时间。在您的情况下,问题在于您编写Num实例的方式:

instance Num a => Num (Unit NoUnit a)

你真正想要的是

instance (Num a, u ~ NoUnit) => Num (Unit u a)

这样,当GHC发现它需要Num (Unit u) a时,就会得出结论,它需要Num au ~ NoUnit。你写它的方式,你在某个地方留下了一些额外实例的可能性。

将左侧的=>右侧的类型构造函数转换为等式约束的技巧通常很有用。