如何通过withKnownNat与withSomeSNat将值转换为类型参数?
GHC.TypeNats中withKnownNat与withSomeSNat的用法解析
函数核心作用
这两个函数的本质是打通运行时动态数值和类型级Nat之间的壁垒:
withSomeSNat:接收一个运行时的Natural值,将其转换为对应类型级Nat的单例SNat n,再传递给一个能处理任意Nat类型的多态回调函数。它的作用是把动态值"提升"到类型层面,让你能在类型约束下处理这个值。withKnownNat:持有SNat n单例时,临时为当前作用域注入KnownNat n约束——有了这个约束,你就能调用依赖它的函数(比如natVal获取类型级Nat对应的数值,或是使用需要该约束的类型类实例)。
你的代码报错原因及修正
你最初的写法之所以报错,是因为props的多态forall n和withKnownNat提供的KnownNat n约束没有绑定到同一个n上。类型检查器无法推导出这两个n是同一个,所以抛出了无法推导KnownNat n0的错误。
修正后的正确写法:
dynat :: [()] dynat = withSomeSNat 3 $ \n -> withKnownNat n props props :: forall n. KnownNat n => [()] props = []
或者用更紧凑的写法:
dynat :: [()] dynat = withSomeSNat 3 (`withKnownNat` props') where -- 这里的n会被withKnownNat的上下文绑定到具体的类型级Nat props' :: KnownNat n => [()] props' = []
实用封装:QuickCheck场景下的工具函数
你封装的applyAsNatType是非常实用的,它把withSomeSNat和withKnownNat的组合逻辑打包,完美适配QuickCheck这类需要动态生成数值、再映射到类型级参数的测试场景:
applyAsNatType :: forall a. (forall n. KnownNat n => SNat n -> a) -> Natural -> a applyAsNatType f x = x `withSomeSNat` go where go :: forall n. SNat n -> a go n = n `withKnownNat` f n
比如你的测试示例中,foo依赖KnownNat n约束和SNat n单例,applyAsNatType foo直接把它转换成Natural -> Property,让QuickCheck可以自动生成不同的Natural值,无缝转换成对应的类型级Nat来测试你的代码:
foo :: forall n. KnownNat n => SNat n -> Property foo _ = monadicIO $ do ps :: Foo n <- liftIO (generate arbitrary) liftIO (putStrLn $ "ps = " <> show ps) test_Tree = testProperty "foo" (applyAsNatType foo)
内容的提问来源于stack exchange,提问作者Daniil Iaitskov
相关产品推荐
相关产品推荐

