我试图理解GHC.TypeLits
,特别是someNatVal
。我了解它在此博客post here中使用的方式,但如上所述,可以使用natVal
实现相同的示例,例如:
isLength :: forall len a. KnownNat len => Integer -> List len a -> Bool
isLength n _ = n == natVal (Proxy :: Proxy len)
someNatVal
是否有natVal
无法使用{{1}}进行重写?
答案 0 :(得分:4)
SomeNat
是存在量化的:
data SomeNat = forall n. KnownNat n => SomeNat (Proxy n)
类似SomeNat
的内容包含,#34;某些n :: Nat
"。 Proxy
是一个允许将n
提升到类型级别以满足类型系统的单例 - 在依赖类型的语言中,我们很少需要这样的构造。我们可以使用SomeNat
更明确地定义GADTs
:
data SomeNat where
SomeNat :: forall n. KnownNat n => Proxy n -> SomeNat
所以SomeNat
包含Nat
,这是静态不知道的。
然后
someNatVal :: Integer -> Maybe SomeNat
读取" someNatVal
收到Integer
,Maybe
返回隐藏Nat
"。我们无法返回KnownNat
,因为Known
表示"在类型级别"已知,但我们对于任意Integer
没有任何了解类型级别。
SomeNat
的逆(模proxy
包装和Proxy
而不是someNatVal
)
natVal :: forall n proxy. KnownNat n => proxy n -> Integer
类似" natVal
的内容会收到类型为Nat
的内容,并返回Nat
转换为Integer
"。
存在量化的数据类型通常为used,用于包装运行时值。假设您要从Vec
读取STDIN
(具有静态已知长度的列表),但您将如何静态计算输入的长度?没有办法。因此,您在相应的存在量化数据类型中包装一个您已阅读的列表,因此说“我不知道长度,但是存在一个"。
然而,在许多情况下它过度杀伤。要对存在量化数据类型执行某些操作,您需要具有一般性:例如:如果您需要找到SomeVec
的元素总和,则必须为任意长度的sumVec
定义Vec
。然后你打开SomeVec
并应用sumVec
,然后说"我不知道被包裹的Vec
的长度,但是sumVec
没有&#39}关心"。但是,您可以directly使用CPS而不是这种包装解缠。
但是,由于这种普遍性,您需要启用ImpredicativeTypes
才能定义具有此类延续的列表。在这种情况下,具有存在量化数据的列表是common pattern,允许绕过ImpredicativeTypes
。
对于您的示例,使用正确定义的Nat
,比较Nat
的版本为lazier,而不是比较Integer
的版本。
答案 1 :(得分:3)
someNatVal
的主要用途是在运行时使用一个值,就好像它是一种在编译时不知道的类型。
我对Can I have an unknown KnownNat?的回答提供了一个非常愚蠢的例子。有一百万种更好的方法来编写一个与那个程序完全相同的程序。但它显示了someNatVal
的作用。它本质上是一个从价值到类型的函数 - 存在性将该类型的范围限制在它不是静态知道的地方。