从任意未知的 Nats 中提取值

Extracting values from arbitrary un-Known Nats

给定类型级列表函数(来自this blog post

type family Length (xs :: [*]) :: Nat where
    Length '[] = 0
    Length (_ ': xs) = 1 + Length xs

'hard-coded' 类型列表长度的值提取按预期工作

t1 :: Integer
t1 = natVal (Proxy :: Proxy (Length '[Int]))

t1 = 1。但是,创建一个从任意列表中获取值的函数似乎失败了。

t2 :: forall (xs :: [*]). Proxy xs -> Integer
t2 _ = natVal (Proxy :: Proxy (Length xs))

报错

    • No instance for (KnownNat (Length xs))
        arising from a use of ‘natVal’

这看起来很不幸,但却是合理的,因为只有文字是 'Known'。但是,如下强制满足约束会导致对模糊类型的抱怨。

unsafeKnownConstr :: c :~: (KnownNat n :: Constraint)
unsafeKnownConstr = unsafeCoerce Refl

t3 :: forall (xs :: [*]). Proxy xs -> Integer
t3 _ = case Proxy :: Proxy (Length xs) of
         p@(_ :: Proxy l) -> case unsafeKnownConstr @(KnownNat l) of
                             Refl -> natVal @l p

具体来说,

    • Could not deduce (KnownNat n0) arising from a use of ‘natVal’
      from the context: KnownNat n0 ~ KnownNat (Length xs)
        bound by a pattern with constructor:
                   Refl :: forall k (a :: k). a :~: a,
                 in a case alternative
...
      The type variable ‘n0’ is ambiguous

GHC (8.10.4) 在这里抱怨什么?或者,是否有另一种方法来提取这些未知的 Nats?

你可以这样做:

t4 :: forall n xs. (n ~ Length xs, KnownNat n) => Proxy xs -> Integer
t4 _ = natVal $ Proxy @n

这表示您的 调用者 必须充分了解类型级别列表 xs 才能将 Length xs 完全解析为固定值,为此他们将能够生成一个 KnownNat 实例。1 但在 t4 内部,您可以对您静态不知道的列表进行操作。

λ t4 $ Proxy @[Int, Char, Bool]
3
it :: Integer

您的尝试没有成功,因为 unsafeKnownConstr 的类型是 forall c n. c :~: KnownNat n。它承诺对于你喜欢的任何约束c,它可以证明对于你喜欢的任何n约束等于KnownNat n

除了这显然是一个错误的承诺之外,当您使用 unsafeKnownConstr @(KnownNat l) 时,您只是在设置 cn 仍然是一个可以实例化为任何东西的类型变量。 GHC 不知道为 n 选择什么;这就是 GHC 抱怨的模棱两可的类型变量。实际上你已经撒谎并告诉它任何任意选择的 n 等于列表的长度,但你实际上没有选择要使用的 n,所以 GHC 无法告诉 what KnownNat 词典你想(错误地)考虑 KnownNat 词典 Length xs.

如果你修复了 unsafeKnownConstr 的第二个类型参数,那么你可以得到这样的东西来编译:

t3 :: forall (xs :: [*]). Proxy xs -> Integer
t3 _ = case Proxy :: Proxy (Length xs) of
         p@(_ :: Proxy l) -> case unsafeKnownConstr @(KnownNat l) @17928 of
                             Refl -> natVal @l p

然后允许您在 GHCI 中执行此操作:

λ t3 $ Proxy @[Int, Char, Bool]
17928
it :: Integer

但是,无论您选择什么值,不安全地强制 KnownNat 约束都不会对您很有用,因为它们确实包含信息:Nat 已知的值!


1 或者他们也可以下注解决它并通过他们自己的类型将约束传递给他们自己的调用者。