Haskell Traversal使用to报错:无法推导Contravariant f及Optic选型疑问
问题解答
一、类型错误的原因与解决
你的代码报错是因为startedAt的实际类型是Fold,但你给它标注了Traversal',两者的类型约束不兼容:
to构造的是Getter,而Getter要求f满足Contravariant约束。Traversal'的类型约束仅要求f是Applicative,不包含Contravariant,因此编译器无法推导所需的约束。
而当你把代码内联到doSomething函数中时,实际是在使用Fold的能力(First类型同时满足Applicative和Contravariant),所以能正常运行。
解决方法有两种:
1. 调整类型签名为Fold'
如果你只需要读取值(不需要修改),直接把类型改成Fold' Object Int即可:
startedAt :: Fold' Object Int startedAt = ix "at" . (_Integral `failing` _String . to timeComponent . _Right `failing` _String . _Integral)
2. 改为Traversal(支持修改)
如果你需要支持修改这个值(符合Traversal的双向能力),需要把to timeComponent . _Right替换为能双向转换的Prism。比如先构造一个Prism,既可以从Text解析出Int,也能把Int转回Text:
_TimeComponent :: Prism' Text Int _TimeComponent = prism showText (\t -> timeComponent t) -- 假设timeComponent是Text -> Either String Int,showText是Int -> Text startedAt :: Traversal' Object Int startedAt = ix "at" . (_Integral `failing` _String . _TimeComponent `failing` _String . _Integral)
Prism和Traversal组合后仍然是Traversal,满足类型约束。
二、Traversal vs Prism的使用场景
用Prism的场景:
- 处理0个或1个元素的部分同构:比如sum类型的分支(
_Left/_Right)、字符串解析为特定类型(同时能反向序列化)。 - 需要双向操作:既能从父类型提取子类型(preview),也能把子类型包装回父类型(review)。
用Traversal的场景:
- 处理0个、1个或多个元素:比如遍历列表中的所有偶数、访问Map中的某个键(可能不存在)、嵌套结构的多个字段。
- 不需要完整的双向同构:只需要读取或修改元素,不需要从子类型构造整个父类型。
简单来说:Prism是“可选的同构”,Traversal是“可遍历的容器访问”。Prism可以看作是特殊的Traversal,但Traversal的适用范围更广,支持多元素操作。
内容的提问来源于stack exchange,提问作者Saravanan
相关产品推荐
相关产品推荐

