我一直在使用带有其他类型级别的奇偶校验信息的自然数。 succ
已通过最直接的方式成功实现:
succ :: Natural p -> Natural (Opp p)
succ = Succ
但是,我仍然在努力让pred
进行类型检查。一个最小的例子:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
data Parity = Even | Odd
type family Opp (p :: Parity) = (r :: Parity) | r -> p where
Opp 'Odd = 'Even
Opp 'Even = 'Odd
data Natural :: Parity -> * where
Zero :: Natural 'Even
Succ :: Natural p -> Natural (Opp p)
pred :: Natural (Opp p) -> Natural p
pred (Succ n) = n
如何成功实施pred
?现在,我遇到了许多不同的大型和复杂类型错误,尤其是could not deduce Opp p ~ p1
。
答案 0 :(得分:6)
给出Parity
的单身人士:
data SParity :: Parity -> Type where
SEven :: SParity Even
SOdd :: SParity Odd
我们可以证明Opp
的内射性
oppInj' :: Opp p ~ Opp q => SParity p -> SParity q -> p :~: q
oppInj' SEven SEven = Refl
oppInj' SOdd SOdd = Refl
现在我们可以定义:
data Natural' :: Parity -> Type where
Zero' :: Natural' Even
Succ' :: SParity p -> Natural' p -> Natural' (Opp p)
pred' :: SParity p -> Natural' (Opp p) -> Natural' p
pred' p (Succ' q n) = case oppInj' p q of Refl -> n
您可以放心地擦除所有单例垃圾:
-- for maximum symmetry, instead of relying on type applications we could
-- just substitute Proxy# in place of SParity everywhere, but meh
oppInj :: forall p q. Opp p ~ Opp q => p :~: q
oppInj = unsafeCoerce Refl -- we know this is OK because oppInj' exists
data Natural :: Parity -> Type where
Zero :: Natural Even
Succ :: Natural p -> Natural (Opp p)
pred :: forall p. Natural (Opp p) -> Natural p
pred (Succ (n :: Natural q)) = case oppInj @p @q of Refl -> n
在Haskell中进行依赖类型编程时,这种模式通常是先处理单例然后擦除它们以改善空间和时间(这里只是一个常数)。通常,您不会写Natural'
或pred'
,但它们对于编写已删除的版本很有用。
PS:请确保处理Zero
情况!
答案 1 :(得分:5)
如@chi所示,
GHC不会将注入性注释用于任何事情,除了允许某些类型(否则会被认为是模棱两可的)。只为了那个。正如人们期望的那样,它们没有被用来从
a ~ b
推断F a ~ F b
。就我个人而言,我认为它们以目前的形式几乎是无用的。
因此,您必须对Natural
进行一些不同的定义:
data Natural :: Parity -> * where
Zero :: Natural 'Even
Succ :: (p ~ Opp s, s ~ Opp p) => Natural p -> Natural s
现在您可以同时获得所需的两个东西。
答案 2 :(得分:4)
这种略有不同的表述如何。请注意Opp
的位置变化:
data Parity = Even | Odd
type family Opp (n :: Parity) = (m :: Parity) | m -> n where
Opp 'Even = 'Odd
Opp 'Odd = 'Even
data Natural :: Parity -> * where
Zero :: Natural 'Even
Succ :: Natural (Opp p) -> Natural p
pred :: Natural p -> Natural (Opp p)
pred (Succ n) = n
这使pred
“顺其自然”。编译器不需要“撤消” Opp
应用程序,而只是“向前工作”。
但是,等等,这是否将问题转移到Succ
构造函数上?实际上,是的,如果我删除注入性注释,则类似
ghci> Succ (Succ Zero)
无法推断。但是为什么以前的内射注释不够,而现在呢?我不知道。