haskell - 见证以前的类型家庭条款不匹配
问题描述
我有以下封闭类型系列:
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
, 和unsafeCoerce
aRefl
在这种Nothing
情况下):
-- (==) 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
推荐阅读
- jenkins - 将 minion 列表传递给 Jenkins 管道,错误:workflowScript:15:美元符号后的非法字符串正文字符;
- python - 检查双端队列为空时的时间复杂度
- ms-access - 什么是刷新MS Access中表中所有记录的好方法
- flutter - 如何将文本放置在标签栏的左侧?
- bootstrap-4 - 如何在 bootstrap-datetimepicker 中设置深色主题?
- flutter - Flutter Screens 层次结构
- spring-boot - 类 java.util.LinkedList 不能转换为类 org.reactivestreams.Publisher
- nginx - 摄入客户端和摄入服务器之间启用 TLS 的通信
- html - 按回车键移动下一步在html的日期字段中不起作用
- asp.net-core - HTTPClientFactory中释放TCP端口的机制是怎样的?