Haskell中如何对Lens类型进行相等性比较
为Lens实现等价性判断的优化方案
原方案的不足
你当前的实现只验证了透镜读操作(^./view)的一致性,存在两个明显缺陷:
- 未覆盖透镜的写行为:合法透镜需要同时满足get-set、set-get、set-set三大定律,仅校验读逻辑无法识别两个读结果一致、但写行为不同的透镜。
- 基于随机采样的等价性不是严格的数学等价:100次采样存在漏判概率,若用
unsafePerformIO封装为Eq实例,会违反Eq类型类要求的确定性、自反性等契约,仅适合测试场景使用,不能用于生产代码逻辑。
更完善的实现方案
场景1:仅用于测试校验透镜等价
建议直接用QuickCheck属性测试覆盖透镜的完整语义,不需要封装为Eq实例:
import Test.QuickCheck import Control.Lens lensEquivalenceProp :: (Arbitrary s, Arbitrary a, Eq s, Eq a) => Lens' s a -> Lens' s a -> Property lensEquivalenceProp l1 l2 = forAll arbitrary $ \s -> forAll arbitrary $ \a -> -- 读逻辑一致 (s ^. l1 == s ^. l2) -- 写后读一致 .&&. (set l1 a s ^. l1 == set l2 a s ^. l2) -- 写后整个结构一致 .&&. (set l1 a s == set l2 a s) -- 各自符合透镜基本定律(可选,若你能保证输入都是合法透镜可省略) .&&. (set l1 (s ^. l1) s == s) .&&. (set l2 (s ^. l2) s == s)
你可以直接用quickCheck (lensEquivalenceProp l1 l2)执行测试,调整测试用例数量的话通过QuickCheck的参数配置即可,不需要手动写循环采样。
场景2:确实需要Eq实例
如果你的业务场景必须用到Eq实例,优先选择穷举方案而非随机采样:
- 若
s和a都是有限小类型,直接穷举所有可能的s和a值做校验,得到的是严格的等价判断,不需要依赖随机和unsafePerformIO。 - 若只能用随机方案,务必固定随机种子,并且仅在测试代码中定义该实例,不要导出到生产库供外部使用,避免其他开发者误用不符合语义的
Eq实例。
注意事项
Haskell标准中函数类型没有Eq实例,本质是因为函数的外延等价性不可判定,透镜作为特殊函数也没有突破这个限制,所有自定义的等价判断都是基于「业务可接受的校验范围」实现的,不要当做通用的Eq实例对外提供。
内容的提问来源于stack exchange,提问作者Daniil Iaitskov
相关产品推荐
相关产品推荐

