如何通过类型级别的奇偶校验检查找到Natural的前身?

时间:2019-07-07 16:55:26

标签: haskell

我一直在使用带有其他类型级别的奇偶校验信息的自然数。 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

3 个答案:

答案 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)

无法推断。但是为什么以前的内射注释不够,而现在呢?我不知道。