如何为包装函数的newtype T1编写Hedgehog生成器以验证Functor实例合规性
解决T1类型的Functor实例属性测试生成器问题
你说得没错,要为T1 a生成随机测试值,确实需要借助hedgehog-fn来生成任意的Int -> a函数——毕竟Hedgehog本身不直接支持生成函数类型的随机值,而hedgehog-fn专门用来处理这类函数生成的需求。下面一步步来实现你需要的genT1,并完成属性测试:
1. 准备依赖
首先确保你的项目里添加了这些依赖(以Cabal为例):
build-depends: base >= 4.14 && < 5, hedgehog >= 1.2, hedgehog-classes >= 0.2, hedgehog-fn >= 0.7
2. 编写genT1生成器
核心思路是:用hedgehog-fn的fn函数生成Int -> a类型的随机函数,再把它包装成T1构造器。完整代码示例如下:
{-# LANGUAGE TypeApplications #-} import Hedgehog import Hedgehog.Classes import Hedgehog.Fn (fn) newtype T1 a = T1 (Int -> a) -- 你的第一个Functor实例(正确实现) instance Functor T1 where fmap f (T1 a) = T1 (f . a) -- 你的第二个Functor实例(和第一个完全等价,因为函数的fmap就是组合) -- instance Functor T1 where -- fmap f (T1 a) = T1 (f <$> a) -- 生成T1 a的随机值:接受a类型的生成器,返回T1 a的生成器 genT1 :: Gen a -> Gen (T1 a) genT1 genA = T1 <$> fn @Int genA
代码解释:
TypeApplications扩展是为了显式指定fn的输入类型为Int(fn是多态函数,需要明确告诉它我们要生成Int -> a的函数)。fn @Int genA会生成一个随机的Int -> a函数:它通过随机采样有限个Int输入和对应的a输出,模拟任意函数的行为,足够覆盖属性测试的需求。- 最后用
T1 <$>把生成的函数包装成T1类型。
3. 在GHCi中运行属性测试
加载上述代码后,直接执行以下命令即可测试Functor实例是否符合函子律:
> lawsCheck (functorLaws genT1)
关于你提到的两个实例的说明:
其实你写的两个实例是完全等价的——因为对于函数类型(->) Int来说,它本身就是Functor,f <$> a的定义就是f . a。所以这两个实例都会通过函子律的测试,不存在错误版本。如果你想找一个违反函子律的实例,可以试试这个:
-- 错误实例,会违反函子律 instance Functor T1 where fmap f (T1 a) = T1 (\x -> f (a (x + 1)))
这个实例会在测试中失败,因为它不满足fmap id = id的核心律。
内容的提问来源于stack exchange,提问作者Jezen Thomas
相关产品推荐
相关产品推荐

