如何在 Haskell 中限制开放世界假设
How to limit the open world assumption in Haskell
为了提高我对 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 Unit
的 Num
实例的唯一有效幻像单元类型是 NoUnit
.
有什么方法可以明确说明这一点,并且在某种程度上,不允许孤儿实例是否允许 GHC 完全放松对开放世界的假设?
在尝试使用部分依赖类型使我的程序更加类型安全时,这种事情已经出现过几次。每当我想为基本情况指定 Num
或 IsString
或 IsList
实例,然后使用自定义值或函数来获取所有其他可能的情况时。
您无法关闭开放世界假设,但有一些方法可以限制它,包括这一次。就您而言,问题在于您编写 Num
实例的方式:
instance Num a => Num (Unit NoUnit a)
你真正想要的是
instance (Num a, u ~ NoUnit) => Num (Unit u a)
这样,当 GHC 发现它需要 Num (Unit u) a
时,它得出结论它需要 Num a
和 u ~ NoUnit
。按照你写的方式,你在某处留下了一些额外实例的可能性。
将 =>
右侧的类型构造函数转换为左侧的等式约束的技巧通常很有用。
为了提高我对 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 Unit
的 Num
实例的唯一有效幻像单元类型是 NoUnit
.
有什么方法可以明确说明这一点,并且在某种程度上,不允许孤儿实例是否允许 GHC 完全放松对开放世界的假设?
在尝试使用部分依赖类型使我的程序更加类型安全时,这种事情已经出现过几次。每当我想为基本情况指定 Num
或 IsString
或 IsList
实例,然后使用自定义值或函数来获取所有其他可能的情况时。
您无法关闭开放世界假设,但有一些方法可以限制它,包括这一次。就您而言,问题在于您编写 Num
实例的方式:
instance Num a => Num (Unit NoUnit a)
你真正想要的是
instance (Num a, u ~ NoUnit) => Num (Unit u a)
这样,当 GHC 发现它需要 Num (Unit u) a
时,它得出结论它需要 Num a
和 u ~ NoUnit
。按照你写的方式,你在某处留下了一些额外实例的可能性。
将 =>
右侧的类型构造函数转换为左侧的等式约束的技巧通常很有用。