无需Template Haskell创建多态Lens时遭遇字段歧义错误求助
解决多态Lens的字段歧义问题
这个歧义问题很常见——当你在多个数据类型里定义了同名的字段_value,编译器没办法自动推断你在定义value lens时想要引用哪一个版本的_value,所以抛出了Ambiguous occurrence '_value'错误。不用Template Haskell的话,我们可以用类型类来优雅地实现多态Lens,下面是具体的解决方法:
方案1:重命名字段(推荐,更清晰)
首先给每个数据类型的字段起独特的名字,避免命名冲突,然后通过类型类抽象value lens的行为:
module Sample where import Control.Lens -- 定义类型类,抽象"拥有value字段"的行为 class HasValue a b | a -> b where value :: Lens' a b -- 定义数据类型,字段名改为独特名称 data A = A { _aValue :: Int } data B = B { _bValue :: Int } data C = C { _cValue :: String } -- 为A实例化HasValue instance HasValue A Int where value = lens _aValue (\old newVal -> old { _aValue = newVal }) -- 为B实例化HasValue instance HasValue B Int where value = lens _bValue (\old newVal -> old { _bValue = newVal }) -- 为C实例化HasValue instance HasValue C String where value = lens _cValue (\old newVal -> old { _cValue = newVal })
这样你就可以用统一的value lens操作不同类型的实例了:
-- 示例用法 getAValue :: Int getAValue = A 10 ^. value -- 得到10 updateBValue :: B updateBValue = B 20 & value .~ 30 -- 把B的value改成30 modifyCValue :: C modifyCValue = C "hello" & value %~ (++ " world") -- 拼接字符串
方案2:保留原字段名,显式指定类型
如果你不想修改字段名,可以在实例中通过显式类型标注告诉编译器你要引用哪个类型的_value字段:
module Sample where import Control.Lens class HasValue a b | a -> b where value :: Lens' a b -- 保留原字段名 data A = A { _value :: Int } data B = B { _value :: Int } data C = C { _value :: String } instance HasValue A Int where -- 显式指定_value是A类型的字段 value = lens (_value :: A -> Int) (\old newVal -> old { _value = newVal }) instance HasValue B Int where -- 显式指定_value是B类型的字段 value = lens (_value :: B -> Int) (\old newVal -> old { _value = newVal }) instance HasValue C String where -- 显式指定_value是C类型的字段 value = lens (_value :: C -> String) (\old newVal -> old { _value = newVal })
这种方式也能解决歧义,但可读性稍差,因为字段名重复容易让人混淆。
为什么原代码会出错?
Haskell的命名空间中,不同数据类型的字段名是共享的——当你写_value时,编译器看到了三个不同的定义(来自A、B、C),而你的value lens想要是多态的,编译器无法确定你要绑定哪一个_value到lens的getter中,因此抛出歧义错误。类型类的方式通过为每个类型单独实现getter和setter,彻底消除了这种歧义。
内容的提问来源于stack exchange,提问作者yilmazhuseyin
相关产品推荐
相关产品推荐

