见证先前的类型族子句不匹配

Witnessing that previous type family clauses didn't match

我有以下封闭型家庭:

type family IsSpecialSize (n :: Nat) :: Bool where
    IsSpecialSize 8 = True
    IsSpecialSize 16 = True
    IsSpecialSize _ = False

我想为这种类型的家庭写一个单例见证人和一个决定者:

data SSpecial (n :: Nat) where
    SSpecial8 :: SSpecial 8
    SSpecial16 :: SSpecial 16
    SNotSpecial :: (IsSpecialSize n ~ False) => SSpecial n

class DecideSSpecial (n :: Nat) where
    specialSize :: SSpecial n

特殊情况很容易涵盖:

instance {-# OVERLAPPING #-} DecideSSpecial 8 where
    specialSize = SSpecial8

instance {-# OVERLAPPING #-} DecideSSpecial 16 where
    specialSize = SSpecial16

但是,我们在使用通用实例时遇到了麻烦。我们不能只写

instance {-# OVERLAPPABLE #-} (KnownNat n) => DecideSSpecial n where
    specialSize = SNotSpecial

因为范围内没有任何东西可以证明 IsSpecialSize n ~ False。我们可以尝试将它添加到最后一个实例的上下文中:

instance {-# OVERLAPPABLE #-} (KnownNat n, IsSpecialSize n ~ False) => DecideSSpecial n where
    specialSize = SNotSpecial

但是我们不能使用它来抽象 n;例如,以下定义无法进行类型检查:

data Unsigned (n :: Nat) where
    U8 :: Word8 -> Unsigned 8
    U16 :: Word16 -> Unsigned 16
    U :: (IsSpecialSize n ~ False) => Integer -> Unsigned n

instance forall n. (KnownNat n) => Num (Unsigned n) where
    fromInteger = case specialSize @n of
        SSpecial8 -> U8 . fromInteger
        SSpecial16 -> U16 . fromInteger
        SNotSpecial -> U . fromInteger

    • Couldn't match type ‘IsSpecialSize n’ with ‘'False’
        arising from a use of ‘specialSize’
    • In the expression: specialSize @n

我可以做的一件事是将 DecideSSpecial n 添加到 Num 实例的上下文中:

instance forall n. (KnownNat n, DecideSSpecial n) => Num (Unsigned n) where

但我非常想避免这种情况;毕竟,从道德上讲,我应该能够判断给定的 KnownNat 是否等于 8、16 或两者都不等于。

目前没有办法安全地做到这一点。

如果有一种方法可以从 KnownNat 约束中获得 运行 时间不平等的证据。

您仍然可以不安全地创建这样的方式(您可以使用 GHC.TypeNats.sameNat,并且在 Nothing 情况下使用 unsafeCoerceRefl

-- (==) and (:~:) from Data.Type.Equality
eqNat :: forall n m. (KnownNat n, KnownNat m) => Either ((n == m) :~: 'False) (n :~: m)
eqNat =
  case GHC.TypeNats.sameNat @n @m Proxy Proxy of
    Just r -> Right r
    Nothing -> Left (unsafeCoerce (Refl :: 'False :~: 'False))
-- The only piece of unsafe code in this answer.

请注意,它并不像您预期​​的那样强大。特别是,不平等不是对称的:((n == m) ~ 'False) 并不意味着 ((m == n) ~ 'False)。所以你必须注意 eqNat.

参数的顺序

由于使用了通用相等性测试,IsSpecialSize 测试也需要使用它:

type IsSpecialSize n = (n == 8) || (n == 16)

最后你可以定义一个实例,实际上是一个简单的函数。

specialSize :: forall n. KnownNat n => SSpecial n
specialSize =
  case eqNat @n @8 of
    Right Refl -> SSpecial8
    Left Refl ->
      case eqNat @n @16 of
        Right Refl -> SSpecial16
        Left Refl -> SNotSpecial