我试图在Haskell中的类型级别实现Integers。首先,我用
实现了自然数字data Zero
data Succ a
然后我用
将其扩展为整数data NegSucc a
然后我决定创建一个增加整数的Increment
类。我是这样做的:
{-# Language FunctionalDependencies #-}
{-# Language UndecidableInstances #-}
{-# Language MultiParamTypeClasses #-}
{-# Language FlexibleInstances #-}
import Prelude ()
-- Peano Naturals --
data Zero
data Succ a
class Peano a
instance Peano Zero
instance (Peano a) => Peano (Succ a)
-- Integers --
data NegSucc a -- `NegSucc a` is -(a+1) so that 0 can only be expressed one way
class Integer a
instance (Peano a) => Integer a
instance (Peano a) => Integer (NegSucc a)
-- Arithmetic Operations --
class Increment a b | a -> b
instance (Peano a) => Increment a (Succ a)
instance Increment (NegSucc Zero) Zero
instance (Peano a) => Increment (NegSucc (Succ a)) (NegSucc a)
然而,当我运行此GHC时,抱怨Functional dependencies conflict between instance declarations
,并引用我的所有三个增量实例。这个错误对我来说没有多大意义,我没有看到单独声明之间的任何冲突。令我更加困惑的是,如果我将第一个实例更改为两个单独的实例
instance (Peano a) => Increment (Succ a) (Succ (Succ a))
instance Increment Zero (Succ Zero)
编译器完全停止抱怨。但是,定义Peano a
的规则告诉我这两件事应该是相同的。
为什么在编写单行版本时会出现函数依赖冲突,但在编写双行版本时却没有?
答案 0 :(得分:8)
这个答案是this comment的扩展,让我了解正在发生的事情
在Haskell类型中,类是一个开放类,这意味着可以在声明之后创建类的新实例。
这意味着我们无法推断NegSucc a
不是Peano
的成员,因为它总是可以在以后声明它。
instance Peano (NegSucc a)
因此当编译器看到
时instance (Peano a) => Increment a (Succ a)
instance Increment (NegSucc Zero) Zero
它无法知道NegSucc Zero
不会是Peano
的实例。如果NegSucc Zero
是Peano
的实例,它将增加到Zero
和Succ (NegSucc Zero)
,这是一个功能依赖冲突。所以我们必须抛出错误。这同样适用于
instance (Peano a) => Increment (NegSucc (Succ a)) (NegSucc a)
此处(NegSucc (Succ a))
也可以是Peano
的实例。
它看起来好像没有冲突的原因是我们隐含地假设没有Peano
的其他实例而不是我们所知道的实例。当我将单个实例转换为两个新的意义时,我做出了正式的假设。
在新代码中
instance (Peano a) => Increment (Succ a) (Succ (Succ a))
instance Increment Zero (Succ Zero)
无法向预先存在的类型类添加任何内容,以使类型与多个冲突的实例匹配。
答案 1 :(得分:4)
这是我在原始版本中看到的错误消息:
negsucc.hs:28:10: error:
Functional dependencies conflict between instance declarations:
instance Peano a => Increment a (Succ a)
-- Defined at negsucc.hs:28:10
instance Increment (NegSucc Zero) Zero
-- Defined at negsucc.hs:29:10
instance Peano a => Increment (NegSucc (Succ a)) (NegSucc a)
-- Defined at negsucc.hs:30:10
我对此的理解是:
a -> b
意味着GHC将"模式匹配"在a
类型上,我们会根据b
发现a
。Peano NegSucc
的实例。 (这只是了解GHC实例选择的工作原理。可以想象,GHC未来的改进可能会在这种情况下有所改善。)instance Peano a => Increment a (Succ a)
将始终由GHC选择,因为它是全能Increment a
。任何其他已定义的实例都将与此实例冲突。现在,UndecidableInstances
的用途是什么?允许冲突的实例,并允许GHC选择适用的最具体的实例?你可能这么认为。我是这么想的。但请注意,错误消息是专门讨论Functional dependencies conflict
,这意味着我并不认为UndecidableInstances能够以这种方式充分实现处理重叠的FunDeps。
-- pseudocode for the fundep a -> b
increment a = Succ a
increment (NegSucc Zero) = Zero
increment (NegSucc (Succ a)) = NegSucc a
-- note that this is just pseudocode; unlike Haskell,
-- instead of trying cases from top to bottom
-- all cases will be tried simultaneously
但是,如果你"扩展&#34>,这个问题就不存在了。正如你所做的那样定义。
-- pseudocode for the fundep a -> b with expanded defs
increment Zero = Succ Zero
increment (Succ a) = Succ (Succ a)
increment (NegSucc Zero) = Zero
increment (NegSucc (Succ a)) = NegSucc a
-- notice how there is now no overlap on the LHS pattern matches
您也可以通过使用原始defs翻转fundep来解决问题。
-- pseudocode for the fundep b -> a with original defs
-- I called it "decrement" instead,
-- because from the b -> a point of view, that is what it does
decrement (Succ a) = a
decrement Zero = NegSucc Zero
decrement (NegSucc a) = NegSucc (Succ a)
-- notice how there is no overlap on the LHS pattern matches
我在这里伸展自己对fundeps的了解,如果我错了,请有人纠正我。